openai 7.23.0 → 7.25.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 +51 -0
- package/README.md +4 -1
- package/client.d.mts +1 -1
- package/client.d.mts.map +1 -1
- package/client.d.ts +1 -1
- package/client.d.ts.map +1 -1
- package/client.js +43 -5
- package/client.js.map +1 -1
- package/client.mjs +43 -5
- package/client.mjs.map +1 -1
- package/internal/responses/canonical-output-text.d.mts +8 -4
- package/internal/responses/canonical-output-text.d.mts.map +1 -1
- package/internal/responses/canonical-output-text.d.ts +8 -4
- package/internal/responses/canonical-output-text.d.ts.map +1 -1
- package/internal/responses/canonical-output-text.js +11 -0
- package/internal/responses/canonical-output-text.js.map +1 -1
- package/internal/responses/canonical-output-text.mjs +11 -0
- package/internal/responses/canonical-output-text.mjs.map +1 -1
- package/internal/responses/response-accumulator.d.mts +12 -1
- package/internal/responses/response-accumulator.d.mts.map +1 -1
- package/internal/responses/response-accumulator.d.ts +12 -1
- package/internal/responses/response-accumulator.d.ts.map +1 -1
- package/internal/responses/response-accumulator.js +99 -25
- package/internal/responses/response-accumulator.js.map +1 -1
- package/internal/responses/response-accumulator.mjs +97 -26
- package/internal/responses/response-accumulator.mjs.map +1 -1
- package/internal/ws.d.mts +6 -2
- package/internal/ws.d.mts.map +1 -1
- package/internal/ws.d.ts +6 -2
- package/internal/ws.d.ts.map +1 -1
- package/internal/ws.js +5 -3
- package/internal/ws.js.map +1 -1
- package/internal/ws.mjs +5 -3
- package/internal/ws.mjs.map +1 -1
- package/lib/live/transcript-grouping.d.mts.map +1 -1
- package/lib/live/transcript-grouping.d.ts.map +1 -1
- package/lib/live/transcript-grouping.js +6 -5
- package/lib/live/transcript-grouping.js.map +1 -1
- package/lib/live/transcript-grouping.mjs +6 -5
- package/lib/live/transcript-grouping.mjs.map +1 -1
- package/lib/responses/responses-websocket-accumulator.d.mts +72 -0
- package/lib/responses/responses-websocket-accumulator.d.mts.map +1 -0
- package/lib/responses/responses-websocket-accumulator.d.ts +72 -0
- package/lib/responses/responses-websocket-accumulator.d.ts.map +1 -0
- package/lib/responses/responses-websocket-accumulator.js +291 -0
- package/lib/responses/responses-websocket-accumulator.js.map +1 -0
- package/lib/responses/responses-websocket-accumulator.mjs +287 -0
- package/lib/responses/responses-websocket-accumulator.mjs.map +1 -0
- package/package.json +1 -1
- package/realtime/translations/ws.d.mts +84 -0
- package/realtime/translations/ws.d.mts.map +1 -0
- package/realtime/translations/ws.d.ts +84 -0
- package/realtime/translations/ws.d.ts.map +1 -0
- package/realtime/translations/ws.js +444 -0
- package/realtime/translations/ws.js.map +1 -0
- package/realtime/translations/ws.mjs +439 -0
- package/realtime/translations/ws.mjs.map +1 -0
- package/resources/audio/speech.d.mts +2 -2
- package/resources/audio/speech.d.mts.map +1 -1
- package/resources/audio/speech.d.ts +2 -2
- package/resources/audio/speech.d.ts.map +1 -1
- package/resources/audio/speech.js +1 -1
- package/resources/audio/speech.mjs +1 -1
- package/resources/audio/transcriptions.d.mts +2 -1
- package/resources/audio/transcriptions.d.mts.map +1 -1
- package/resources/audio/transcriptions.d.ts +2 -1
- package/resources/audio/transcriptions.d.ts.map +1 -1
- package/resources/audio/transcriptions.js.map +1 -1
- package/resources/audio/transcriptions.mjs.map +1 -1
- package/resources/beta/agents/agents.d.mts +577 -18
- package/resources/beta/agents/agents.d.mts.map +1 -1
- package/resources/beta/agents/agents.d.ts +577 -18
- package/resources/beta/agents/agents.d.ts.map +1 -1
- package/resources/beta/agents/agents.js.map +1 -1
- package/resources/beta/agents/agents.mjs.map +1 -1
- package/resources/beta/agents/environments/templates.d.mts +62 -9
- package/resources/beta/agents/environments/templates.d.mts.map +1 -1
- package/resources/beta/agents/environments/templates.d.ts +62 -9
- package/resources/beta/agents/environments/templates.d.ts.map +1 -1
- package/resources/beta/agents/index.d.mts +1 -1
- package/resources/beta/agents/index.d.mts.map +1 -1
- package/resources/beta/agents/index.d.ts +1 -1
- package/resources/beta/agents/index.d.ts.map +1 -1
- package/resources/beta/agents/index.js.map +1 -1
- package/resources/beta/agents/index.mjs.map +1 -1
- package/resources/beta/agents/sessions/events.d.mts +10 -7
- package/resources/beta/agents/sessions/events.d.mts.map +1 -1
- package/resources/beta/agents/sessions/events.d.ts +10 -7
- package/resources/beta/agents/sessions/events.d.ts.map +1 -1
- package/resources/beta/agents/sessions/events.js +10 -7
- package/resources/beta/agents/sessions/events.js.map +1 -1
- package/resources/beta/agents/sessions/events.mjs +10 -7
- package/resources/beta/agents/sessions/events.mjs.map +1 -1
- package/resources/beta/agents/sessions/sessions.d.mts +4 -2
- package/resources/beta/agents/sessions/sessions.d.mts.map +1 -1
- package/resources/beta/agents/sessions/sessions.d.ts +4 -2
- package/resources/beta/agents/sessions/sessions.d.ts.map +1 -1
- package/resources/beta/agents/sessions/sessions.js.map +1 -1
- package/resources/beta/agents/sessions/sessions.mjs.map +1 -1
- package/resources/beta/agents/vaults/credentials.d.mts +24 -7
- package/resources/beta/agents/vaults/credentials.d.mts.map +1 -1
- package/resources/beta/agents/vaults/credentials.d.ts +24 -7
- package/resources/beta/agents/vaults/credentials.d.ts.map +1 -1
- package/resources/beta/agents/vaults/credentials.js +2 -6
- package/resources/beta/agents/vaults/credentials.js.map +1 -1
- package/resources/beta/agents/vaults/credentials.mjs +2 -6
- package/resources/beta/agents/vaults/credentials.mjs.map +1 -1
- package/resources/beta/beta.d.mts +2 -2
- package/resources/beta/beta.d.mts.map +1 -1
- package/resources/beta/beta.d.ts +2 -2
- package/resources/beta/beta.d.ts.map +1 -1
- package/resources/beta/beta.js.map +1 -1
- package/resources/beta/beta.mjs.map +1 -1
- package/resources/beta/index.d.mts +1 -1
- package/resources/beta/index.d.mts.map +1 -1
- package/resources/beta/index.d.ts +1 -1
- package/resources/beta/index.d.ts.map +1 -1
- package/resources/beta/index.js.map +1 -1
- package/resources/beta/index.mjs.map +1 -1
- package/resources/beta/responses/responses.d.mts +69 -10
- package/resources/beta/responses/responses.d.mts.map +1 -1
- package/resources/beta/responses/responses.d.ts +69 -10
- package/resources/beta/responses/responses.d.ts.map +1 -1
- package/resources/beta/responses/responses.js.map +1 -1
- package/resources/beta/responses/responses.mjs.map +1 -1
- package/resources/files.d.mts +2 -2
- package/resources/files.d.mts.map +1 -1
- package/resources/files.d.ts +2 -2
- package/resources/files.d.ts.map +1 -1
- package/resources/index.d.mts +1 -1
- package/resources/index.d.mts.map +1 -1
- package/resources/index.d.ts +1 -1
- package/resources/index.d.ts.map +1 -1
- package/resources/live/forks/ws-base.js +1 -1
- package/resources/live/forks/ws-base.js.map +1 -1
- package/resources/live/forks/ws-base.mjs +1 -1
- package/resources/live/forks/ws-base.mjs.map +1 -1
- package/resources/live/sideband/ws-base.js +1 -1
- package/resources/live/sideband/ws-base.js.map +1 -1
- package/resources/live/sideband/ws-base.mjs +1 -1
- package/resources/live/sideband/ws-base.mjs.map +1 -1
- package/resources/live/ws-base.js +1 -1
- package/resources/live/ws-base.js.map +1 -1
- package/resources/live/ws-base.mjs +1 -1
- package/resources/live/ws-base.mjs.map +1 -1
- package/resources/realtime/index.d.mts +1 -0
- package/resources/realtime/index.d.mts.map +1 -1
- package/resources/realtime/index.d.ts +1 -0
- package/resources/realtime/index.d.ts.map +1 -1
- package/resources/realtime/index.js +3 -1
- package/resources/realtime/index.js.map +1 -1
- package/resources/realtime/index.mjs +1 -0
- package/resources/realtime/index.mjs.map +1 -1
- package/resources/realtime/realtime.d.mts +4 -0
- package/resources/realtime/realtime.d.mts.map +1 -1
- package/resources/realtime/realtime.d.ts +4 -0
- package/resources/realtime/realtime.d.ts.map +1 -1
- package/resources/realtime/realtime.js +4 -0
- package/resources/realtime/realtime.js.map +1 -1
- package/resources/realtime/realtime.mjs +4 -0
- package/resources/realtime/realtime.mjs.map +1 -1
- package/resources/realtime/translations/client-secrets.d.mts +67 -0
- package/resources/realtime/translations/client-secrets.d.mts.map +1 -0
- package/resources/realtime/translations/client-secrets.d.ts +67 -0
- package/resources/realtime/translations/client-secrets.d.ts.map +1 -0
- package/resources/realtime/translations/client-secrets.js +39 -0
- package/resources/realtime/translations/client-secrets.js.map +1 -0
- package/resources/realtime/translations/client-secrets.mjs +35 -0
- package/resources/realtime/translations/client-secrets.mjs.map +1 -0
- package/resources/realtime/translations/index.d.mts +3 -0
- package/resources/realtime/translations/index.d.mts.map +1 -0
- package/resources/realtime/translations/index.d.ts +3 -0
- package/resources/realtime/translations/index.d.ts.map +1 -0
- package/resources/realtime/translations/index.js +9 -0
- package/resources/realtime/translations/index.js.map +1 -0
- package/resources/realtime/translations/index.mjs +4 -0
- package/resources/realtime/translations/index.mjs.map +1 -0
- package/resources/realtime/translations/translations.d.mts +10 -0
- package/resources/realtime/translations/translations.d.mts.map +1 -0
- package/resources/realtime/translations/translations.d.ts +10 -0
- package/resources/realtime/translations/translations.d.ts.map +1 -0
- package/resources/realtime/translations/translations.js +17 -0
- package/resources/realtime/translations/translations.js.map +1 -0
- package/resources/realtime/translations/translations.mjs +12 -0
- package/resources/realtime/translations/translations.mjs.map +1 -0
- package/resources/realtime/translations.d.mts +2 -0
- package/resources/realtime/translations.d.mts.map +1 -0
- package/resources/realtime/translations.d.ts +2 -0
- package/resources/realtime/translations.d.ts.map +1 -0
- package/resources/realtime/translations.js +6 -0
- package/resources/realtime/translations.js.map +1 -0
- package/resources/realtime/translations.mjs +3 -0
- package/resources/realtime/translations.mjs.map +1 -0
- package/resources/responses/responses.d.mts +66 -7
- package/resources/responses/responses.d.mts.map +1 -1
- package/resources/responses/responses.d.ts +66 -7
- package/resources/responses/responses.d.ts.map +1 -1
- package/resources/responses/responses.js.map +1 -1
- package/resources/responses/responses.mjs.map +1 -1
- package/resources/shared.d.mts +1 -1
- package/resources/shared.d.mts.map +1 -1
- package/resources/shared.d.ts +1 -1
- package/resources/shared.d.ts.map +1 -1
- package/src/client.ts +42 -6
- package/src/internal/responses/canonical-output-text.ts +30 -5
- package/src/internal/responses/response-accumulator.ts +155 -56
- package/src/internal/ws.ts +5 -3
- package/src/lib/live/transcript-grouping.ts +6 -5
- package/src/lib/responses/responses-websocket-accumulator.ts +374 -0
- package/src/realtime/translations/ws.ts +539 -0
- package/src/resources/audio/speech.ts +4 -1
- package/src/resources/audio/transcriptions.ts +2 -1
- package/src/resources/beta/agents/agents.ts +699 -12
- package/src/resources/beta/agents/environments/templates.ts +70 -9
- package/src/resources/beta/agents/index.ts +3 -0
- package/src/resources/beta/agents/sessions/events.ts +10 -7
- package/src/resources/beta/agents/sessions/sessions.ts +4 -2
- package/src/resources/beta/agents/vaults/credentials.ts +21 -7
- package/src/resources/beta/beta.ts +6 -0
- package/src/resources/beta/index.ts +3 -0
- package/src/resources/beta/responses/responses.ts +75 -6
- package/src/resources/files.ts +3 -3
- package/src/resources/index.ts +1 -1
- package/src/resources/live/forks/ws-base.ts +1 -1
- package/src/resources/live/sideband/ws-base.ts +1 -1
- package/src/resources/live/ws-base.ts +1 -1
- package/src/resources/realtime/api.md +8 -0
- package/src/resources/realtime/index.ts +1 -0
- package/src/resources/realtime/realtime.ts +6 -0
- package/src/resources/realtime/translations/client-secrets.ts +93 -0
- package/src/resources/realtime/translations/index.ts +4 -0
- package/src/resources/realtime/translations/translations.ts +15 -0
- package/src/resources/realtime/translations.ts +3 -0
- package/src/resources/responses/responses.ts +72 -6
- package/src/resources/shared.ts +1 -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
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
import * as WS from 'ws';
|
|
2
|
+
import type { ClientOptions, OpenAI } from '../../client';
|
|
3
|
+
import { EventEmitter } from '../../lib/EventEmitter';
|
|
4
|
+
import { assertX509WebSocketSupported } from '../../internal/auth/x509-workload-identity-auth';
|
|
5
|
+
import { brand_privateBedrockClient } from '../../internal/bedrock';
|
|
6
|
+
import { isRunningInBrowser } from '../../internal/detect-platform';
|
|
7
|
+
import { resolveRealtimeAPIKey } from '../../internal/realtime-credentials';
|
|
8
|
+
import { snapshotWebSocketCredentials } from '../../internal/ws';
|
|
9
|
+
import { ReadyState } from '../../internal/ws-adapter';
|
|
10
|
+
import { NodeWebSocket } from '../../internal/ws-adapter-node';
|
|
11
|
+
import type {
|
|
12
|
+
RealtimeError,
|
|
13
|
+
RealtimeErrorEvent,
|
|
14
|
+
RealtimeTranslationClientEvent,
|
|
15
|
+
RealtimeTranslationServerEvent,
|
|
16
|
+
RealtimeTranslationSession,
|
|
17
|
+
} from '../../resources/realtime/realtime';
|
|
18
|
+
import { isAzure, OpenAIRealtimeError } from '../internal-base';
|
|
19
|
+
|
|
20
|
+
/** An event envelope, including event types added by the service in the future. */
|
|
21
|
+
export interface RealtimeTranslationEvent {
|
|
22
|
+
type: string;
|
|
23
|
+
// oxlint-disable-next-line anti-slop/no-unsafe-dictionary-type -- Future wire events retain fields whose schema is not yet known to this SDK.
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Options for a Node.js translation session. */
|
|
28
|
+
export interface RealtimeTranslationConnectOptions {
|
|
29
|
+
/** Translation model used to create the session. */
|
|
30
|
+
model: string;
|
|
31
|
+
/** Node `ws` options. Redirects are always disabled. */
|
|
32
|
+
options?:
|
|
33
|
+
| (Omit<WS.ClientOptions, 'headers'> & {
|
|
34
|
+
/** Case-insensitive overrides; null removes a default and undefined preserves it. */
|
|
35
|
+
headers?: Record<string, string | null | undefined> | undefined;
|
|
36
|
+
})
|
|
37
|
+
| undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type TranslationEvents = {
|
|
41
|
+
event: (event: RealtimeTranslationServerEvent | RealtimeTranslationEvent) => void;
|
|
42
|
+
error: (error: OpenAIRealtimeError) => void;
|
|
43
|
+
} & {
|
|
44
|
+
[Type in Exclude<RealtimeTranslationServerEvent['type'], 'error'>]: (
|
|
45
|
+
event: Extract<RealtimeTranslationServerEvent, { type: Type }>,
|
|
46
|
+
) => void;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function parseEvent(data: string): RealtimeTranslationEvent {
|
|
50
|
+
let event: unknown;
|
|
51
|
+
try {
|
|
52
|
+
event = JSON.parse(data);
|
|
53
|
+
} catch {
|
|
54
|
+
throw new OpenAIRealtimeError('Could not parse translation WebSocket event as JSON.', null);
|
|
55
|
+
}
|
|
56
|
+
if (
|
|
57
|
+
typeof event !== 'object' ||
|
|
58
|
+
event === null ||
|
|
59
|
+
Array.isArray(event) ||
|
|
60
|
+
typeof Object.getOwnPropertyDescriptor(event, 'type')?.value !== 'string'
|
|
61
|
+
) {
|
|
62
|
+
throw new OpenAIRealtimeError('Translation event must be an object with a string type.', null);
|
|
63
|
+
}
|
|
64
|
+
// SAFETY: The envelope was checked above; payload fields remain unknown until selected by event type.
|
|
65
|
+
return event as RealtimeTranslationEvent;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Copies a freshly parsed JSON tree's containers, sharing its immutable string payloads. */
|
|
69
|
+
function copyEvent(event: RealtimeTranslationEvent): RealtimeTranslationEvent {
|
|
70
|
+
const copy = { ...event };
|
|
71
|
+
const containers: object[] = [copy];
|
|
72
|
+
for (const container of containers) {
|
|
73
|
+
for (const [key, value] of Object.entries(container)) {
|
|
74
|
+
if (typeof value === 'object' && value !== null) {
|
|
75
|
+
const child = Array.isArray(value) ? [...value] : { ...value };
|
|
76
|
+
Object.defineProperty(container, key, {
|
|
77
|
+
value: child,
|
|
78
|
+
writable: true,
|
|
79
|
+
enumerable: true,
|
|
80
|
+
configurable: true,
|
|
81
|
+
});
|
|
82
|
+
containers.push(child);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return copy;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type RequiredFields<T> = {
|
|
90
|
+
[Key in keyof T as T[Key] extends Required<T>[Key] ? Key : never]-?: (value: unknown) => boolean;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function isObject(value: unknown): value is object {
|
|
94
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isString(value: unknown): value is string {
|
|
98
|
+
return typeof value === 'string';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function hasRequiredFields(value: unknown, fields: Record<string, (field: unknown) => boolean>): boolean {
|
|
102
|
+
return (
|
|
103
|
+
isObject(value) &&
|
|
104
|
+
Object.entries(fields).every(([key, check]) => check(Object.getOwnPropertyDescriptor(value, key)?.value))
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function hasOptionalFields(value: unknown, fields: Record<string, (field: unknown) => boolean>): boolean {
|
|
109
|
+
return (
|
|
110
|
+
isObject(value) &&
|
|
111
|
+
Object.entries(fields).every(([key, check]) => {
|
|
112
|
+
const field = Object.getOwnPropertyDescriptor(value, key);
|
|
113
|
+
return field === undefined || check(field.value);
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const inputAudioFields = {
|
|
119
|
+
transcription: (value: unknown) => value === null || hasRequiredFields(value, { model: isString }),
|
|
120
|
+
noise_reduction: (value: unknown) =>
|
|
121
|
+
value === null ||
|
|
122
|
+
hasRequiredFields(value, {
|
|
123
|
+
type: (kind: unknown) => kind === 'near_field' || kind === 'far_field',
|
|
124
|
+
}),
|
|
125
|
+
} satisfies RequiredFields<Required<RealtimeTranslationSession.Audio.Input>>;
|
|
126
|
+
|
|
127
|
+
const audioFields = {
|
|
128
|
+
input: (value: unknown) => hasOptionalFields(value, inputAudioFields),
|
|
129
|
+
output: (value: unknown) => hasOptionalFields(value, { language: isString }),
|
|
130
|
+
} satisfies RequiredFields<Required<RealtimeTranslationSession.Audio>>;
|
|
131
|
+
|
|
132
|
+
const sessionFields = {
|
|
133
|
+
id: isString,
|
|
134
|
+
model: isString,
|
|
135
|
+
expires_at: (value: unknown) => typeof value === 'number',
|
|
136
|
+
type: (value: unknown) => value === 'translation',
|
|
137
|
+
audio: (value: unknown) => hasOptionalFields(value, audioFields),
|
|
138
|
+
} satisfies RequiredFields<RealtimeTranslationSession>;
|
|
139
|
+
|
|
140
|
+
function isOptionalNullableString(value: unknown): boolean {
|
|
141
|
+
return value === undefined || value === null || isString(value);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isOptionalNumber(value: unknown): boolean {
|
|
145
|
+
return value === undefined || typeof value === 'number';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const errorFields = {
|
|
149
|
+
message: isString,
|
|
150
|
+
type: isString,
|
|
151
|
+
code: isOptionalNullableString,
|
|
152
|
+
event_id: isOptionalNullableString,
|
|
153
|
+
param: isOptionalNullableString,
|
|
154
|
+
} satisfies RequiredFields<Required<RealtimeError>>;
|
|
155
|
+
const commonFields = { type: isString, event_id: isString };
|
|
156
|
+
const deltaFields = {
|
|
157
|
+
...commonFields,
|
|
158
|
+
delta: isString,
|
|
159
|
+
elapsed_ms: (value: unknown) => value === null || isOptionalNumber(value),
|
|
160
|
+
};
|
|
161
|
+
const sessionEventFields = {
|
|
162
|
+
...commonFields,
|
|
163
|
+
session: (value: unknown) => hasRequiredFields(value, sessionFields),
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Regeneration that adds an event or field must also update its dispatcher.
|
|
167
|
+
const translationEventFields = {
|
|
168
|
+
'session.closed': commonFields,
|
|
169
|
+
'session.input_transcript.delta': deltaFields,
|
|
170
|
+
'session.output_transcript.delta': deltaFields,
|
|
171
|
+
'session.output_audio.delta': {
|
|
172
|
+
...deltaFields,
|
|
173
|
+
channels: isOptionalNumber,
|
|
174
|
+
format: (value: unknown) => value === undefined || value === 'pcm16',
|
|
175
|
+
sample_rate: isOptionalNumber,
|
|
176
|
+
},
|
|
177
|
+
'session.created': sessionEventFields,
|
|
178
|
+
'session.updated': sessionEventFields,
|
|
179
|
+
error: { ...commonFields, error: (value: unknown) => hasRequiredFields(value, errorFields) },
|
|
180
|
+
} satisfies {
|
|
181
|
+
[Event in RealtimeTranslationServerEvent as Event['type']]: RequiredFields<Required<Event>>;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** Check the required fields before exposing an envelope through a typed listener. */
|
|
185
|
+
function isCompleteTranslationEvent(event: RealtimeTranslationEvent): boolean {
|
|
186
|
+
// SAFETY: Only the above schema-checked map's own data properties can supply a validator.
|
|
187
|
+
const fields = Object.getOwnPropertyDescriptor(translationEventFields, event.type)?.value as
|
|
188
|
+
| Record<string, (field: unknown) => boolean>
|
|
189
|
+
| undefined;
|
|
190
|
+
return fields !== undefined && hasRequiredFields(event, fields);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function buildTranslationURL(client: OpenAI, model: string): URL {
|
|
194
|
+
if (typeof model !== 'string' || !model) {
|
|
195
|
+
throw new Error('A translation model is required.');
|
|
196
|
+
}
|
|
197
|
+
const endpoint = new URL(client.baseURL);
|
|
198
|
+
endpoint.pathname = `${endpoint.pathname.replace(/\/$/u, '')}/realtime/translations`;
|
|
199
|
+
const url = new URL(client.buildURL(endpoint.toString(), { model }));
|
|
200
|
+
if (url.protocol !== 'https:') {
|
|
201
|
+
throw new Error('The translation endpoint must use HTTPS.');
|
|
202
|
+
}
|
|
203
|
+
if (url.searchParams.has('intent')) {
|
|
204
|
+
throw new Error('Realtime translation does not accept an intent query parameter.');
|
|
205
|
+
}
|
|
206
|
+
url.hash = '';
|
|
207
|
+
url.protocol = 'wss:';
|
|
208
|
+
return url;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Node.js translation WebSocket. Install the optional `ws` peer dependency.
|
|
213
|
+
* Wait for `socket` to open before sending. Events are never buffered or replayed.
|
|
214
|
+
* Compression is off by default; set `options.perMessageDeflate` to opt in.
|
|
215
|
+
*/
|
|
216
|
+
// oxlint-disable-next-line unicorn/prefer-event-target -- Reuse the SDK typed emitter and its public on/off event contract.
|
|
217
|
+
export class OpenAIRealtimeTranslationWS extends EventEmitter<TranslationEvents> {
|
|
218
|
+
/** Adapter exposing lifecycle events and the underlying Node socket as `platformSocket`. */
|
|
219
|
+
readonly socket: NodeWebSocket;
|
|
220
|
+
/** The configured endpoint and model for this connection. */
|
|
221
|
+
readonly url: URL;
|
|
222
|
+
/** Sends the protocol close once; use `finish` to await trailing output. */
|
|
223
|
+
readonly session = { close: (): void => this.send({ type: 'session.close' }) };
|
|
224
|
+
|
|
225
|
+
private _inputClosed = false;
|
|
226
|
+
private _terminalReceived = false;
|
|
227
|
+
private _terminalDelivered = false;
|
|
228
|
+
private _transportClosed = false;
|
|
229
|
+
private _failure: OpenAIRealtimeError | undefined;
|
|
230
|
+
private _finishPromise: Promise<void> | undefined;
|
|
231
|
+
private _resolveFinish: (() => void) | undefined;
|
|
232
|
+
private _rejectFinish: ((error: OpenAIRealtimeError) => void) | undefined;
|
|
233
|
+
private _finishTimer: ReturnType<typeof setTimeout> | undefined;
|
|
234
|
+
private _closeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
235
|
+
private _signal: AbortSignal | undefined;
|
|
236
|
+
|
|
237
|
+
private constructor(url: URL, options: WS.ClientOptions) {
|
|
238
|
+
super();
|
|
239
|
+
this.url = url;
|
|
240
|
+
this.socket = new NodeWebSocket(new WS.WebSocket(url, options));
|
|
241
|
+
this.socket.on('message', this._onMessage);
|
|
242
|
+
this.socket.on('error', this._onError);
|
|
243
|
+
this.socket.on('close', this._onClose);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Resolves the API key and starts connecting; resolves before the socket opens. */
|
|
247
|
+
static async create(
|
|
248
|
+
client: OpenAI,
|
|
249
|
+
props: RealtimeTranslationConnectOptions,
|
|
250
|
+
): Promise<OpenAIRealtimeTranslationWS> {
|
|
251
|
+
// SAFETY: Probe optional host capabilities without requiring Node ambient types in published source.
|
|
252
|
+
const scope = globalThis as { process?: { versions?: { node?: string } } };
|
|
253
|
+
if (isRunningInBrowser() || !scope.process?.versions?.node) {
|
|
254
|
+
throw new Error('Realtime translation WebSockets require Node.js.');
|
|
255
|
+
}
|
|
256
|
+
assertX509WebSocketSupported(client);
|
|
257
|
+
// SAFETY: OpenAI owns these options; this read only rejects unsupported authentication modes.
|
|
258
|
+
const clientOptions: ClientOptions = client['_options'];
|
|
259
|
+
if (
|
|
260
|
+
isAzure(client) ||
|
|
261
|
+
brand_privateBedrockClient in client ||
|
|
262
|
+
clientOptions.provider ||
|
|
263
|
+
clientOptions.workloadIdentity
|
|
264
|
+
) {
|
|
265
|
+
throw new Error('Realtime translation WebSockets require an ordinary OpenAI API-key client.');
|
|
266
|
+
}
|
|
267
|
+
const url = buildTranslationURL(client, props.model);
|
|
268
|
+
const { apiKey } = await resolveRealtimeAPIKey(client);
|
|
269
|
+
if (!apiKey) {
|
|
270
|
+
throw new Error('Realtime translation WebSockets require an API key.');
|
|
271
|
+
}
|
|
272
|
+
const headers = new Map(
|
|
273
|
+
Object.entries(client._buildWebSocketHeaders({ Authorization: `Bearer ${apiKey}` })),
|
|
274
|
+
);
|
|
275
|
+
for (const [name, value] of Object.entries(props.options?.headers ?? {})) {
|
|
276
|
+
if (value === null) {
|
|
277
|
+
headers.delete(name.toLowerCase());
|
|
278
|
+
} else if (value !== undefined) {
|
|
279
|
+
headers.set(name.toLowerCase(), value);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const options = {
|
|
283
|
+
...props.options,
|
|
284
|
+
maxPayload: props.options?.maxPayload ?? 0,
|
|
285
|
+
perMessageDeflate: props.options?.perMessageDeflate ?? false,
|
|
286
|
+
headers: Object.fromEntries(headers),
|
|
287
|
+
followRedirects: false,
|
|
288
|
+
};
|
|
289
|
+
snapshotWebSocketCredentials(options);
|
|
290
|
+
return new OpenAIRealtimeTranslationWS(url, options);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Sends a typed event, future event envelope, or raw JSON envelope after the socket opens. */
|
|
294
|
+
send(event: RealtimeTranslationClientEvent | RealtimeTranslationEvent | string): void {
|
|
295
|
+
const data = typeof event === 'string' ? event : JSON.stringify(event);
|
|
296
|
+
const envelope = parseEvent(data);
|
|
297
|
+
if (envelope.type === 'session.close') {
|
|
298
|
+
if (this._inputClosed) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
this._inputClosed = true;
|
|
302
|
+
} else if (this._inputClosed) {
|
|
303
|
+
throw new OpenAIRealtimeError('Translation input is closed.', null);
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
if (this.socket.readyState !== ReadyState.OPEN) {
|
|
307
|
+
throw new Error('The translation WebSocket is not open.');
|
|
308
|
+
}
|
|
309
|
+
this.socket.send(data);
|
|
310
|
+
} catch {
|
|
311
|
+
const error = new OpenAIRealtimeError('Could not send translation WebSocket event.', null);
|
|
312
|
+
this._fail(error);
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Stops input, sends `session.close` once, and delivers all events through `session.closed`.
|
|
319
|
+
* The first call owns the finite deadline and optional cancellation signal. Repeated calls
|
|
320
|
+
* share its result. API error events remain observable and do not end the drain.
|
|
321
|
+
* A timeout, abort, or transport failure rejects; no connection or input is replayed.
|
|
322
|
+
* Resolves after terminal delivery and transport closure. The deadline includes transport
|
|
323
|
+
* cleanup; a stalled close handshake is terminated and rejects the operation.
|
|
324
|
+
*/
|
|
325
|
+
finish({ timeoutMs, signal }: { timeoutMs: number; signal?: AbortSignal | undefined }): Promise<void> {
|
|
326
|
+
if (this._finishPromise) {
|
|
327
|
+
return this._finishPromise;
|
|
328
|
+
}
|
|
329
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2_147_483_647) {
|
|
330
|
+
return Promise.reject(
|
|
331
|
+
new Error('timeoutMs must be a positive finite WebSocket deadline of at most 2147483647.'),
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
// oxlint-disable-next-line promise/avoid-new -- The existing socket dispatcher settles this completion; there is no second event reader.
|
|
335
|
+
this._finishPromise = new Promise<void>((resolve, reject) => {
|
|
336
|
+
this._resolveFinish = resolve;
|
|
337
|
+
this._rejectFinish = reject;
|
|
338
|
+
});
|
|
339
|
+
if (this._transportClosed) {
|
|
340
|
+
this._settleFinish();
|
|
341
|
+
return this._finishPromise;
|
|
342
|
+
}
|
|
343
|
+
clearTimeout(this._closeTimer);
|
|
344
|
+
this._closeTimer = undefined;
|
|
345
|
+
this._signal = signal;
|
|
346
|
+
this._finishTimer = setTimeout(() => {
|
|
347
|
+
this._fail(
|
|
348
|
+
new OpenAIRealtimeError(
|
|
349
|
+
'Timed out finishing the translation session and closing its transport.',
|
|
350
|
+
null,
|
|
351
|
+
),
|
|
352
|
+
);
|
|
353
|
+
}, timeoutMs);
|
|
354
|
+
signal?.addEventListener('abort', this._onAbort, { once: true });
|
|
355
|
+
if (signal?.aborted) {
|
|
356
|
+
this._onAbort();
|
|
357
|
+
} else if (!this._terminalReceived && !this._failure) {
|
|
358
|
+
try {
|
|
359
|
+
this.session.close();
|
|
360
|
+
} catch {
|
|
361
|
+
// send already records the failure and terminates the connection.
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
this._settleFinish();
|
|
365
|
+
return this._finishPromise;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** Closes the transport without waiting for terminal output. Prefer `finish` for a complete session. */
|
|
369
|
+
close(): void {
|
|
370
|
+
this._inputClosed = true;
|
|
371
|
+
this._closeTransport();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private _onMessage = (data: string | Buffer): void => {
|
|
375
|
+
if (this._terminalReceived) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const wireData = data.toString();
|
|
379
|
+
let event: RealtimeTranslationEvent;
|
|
380
|
+
try {
|
|
381
|
+
event = parseEvent(wireData);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
// SAFETY: parseEvent only throws normalized, payload-free OpenAIRealtimeError instances.
|
|
384
|
+
this._reportError(error as OpenAIRealtimeError);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const { type } = event;
|
|
388
|
+
const typed = isCompleteTranslationEvent(event);
|
|
389
|
+
const terminal = type === 'session.closed' && typed;
|
|
390
|
+
if (terminal) {
|
|
391
|
+
this._inputClosed = true;
|
|
392
|
+
this._terminalReceived = true;
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
try {
|
|
396
|
+
// Raw listeners may mutate their event. Keep typed dispatch and finish
|
|
397
|
+
// anchored to the original validated wire event, including nested data.
|
|
398
|
+
if (this._hasListener('event')) {
|
|
399
|
+
this._emit('event', typed ? copyEvent(event) : event);
|
|
400
|
+
}
|
|
401
|
+
} finally {
|
|
402
|
+
if (type === 'error' && typed) {
|
|
403
|
+
// SAFETY: The error envelope is preserved as server data, as for other Realtime events.
|
|
404
|
+
// oxlint-disable-next-line anti-slop/no-chained-type-assertions -- Forward server error fields unchanged through the existing Realtime error wrapper.
|
|
405
|
+
const apiErrorEvent = event as unknown as RealtimeErrorEvent;
|
|
406
|
+
const error = new OpenAIRealtimeError(
|
|
407
|
+
`Translation API error: ${apiErrorEvent.error.message}`,
|
|
408
|
+
apiErrorEvent,
|
|
409
|
+
);
|
|
410
|
+
this._reportError(error);
|
|
411
|
+
} else if (type !== 'error' && typed) {
|
|
412
|
+
// SAFETY: The wire discriminator selects its listener; future event names remain visible on `event`.
|
|
413
|
+
this._emit(type as Exclude<keyof TranslationEvents, 'event' | 'error'>, event as never);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
} finally {
|
|
417
|
+
if (terminal) {
|
|
418
|
+
this._terminalDelivered = true;
|
|
419
|
+
try {
|
|
420
|
+
this._closeTransport();
|
|
421
|
+
} finally {
|
|
422
|
+
this._settleFinish();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
private _onError = (cause: Error): void => {
|
|
429
|
+
// Closing before open also emits a ws error. The caller already requested
|
|
430
|
+
// cleanup; _onClose still records an incomplete drain for a later finish().
|
|
431
|
+
if (this._closeTimer !== undefined && !this._terminalReceived && !this._failure) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const error = new OpenAIRealtimeError('Translation WebSocket transport failed.', null);
|
|
435
|
+
Object.defineProperty(error, 'cause', { value: cause, writable: true, configurable: true });
|
|
436
|
+
this._fail(error);
|
|
437
|
+
// finish already rejects transport failures; don't report that same failure a second time.
|
|
438
|
+
if (this._hasListener('error') || !this._finishPromise) {
|
|
439
|
+
this._reportError(error);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
private _reportError(error: OpenAIRealtimeError): void {
|
|
444
|
+
if (this._hasListener('error')) {
|
|
445
|
+
this._emit('error', error);
|
|
446
|
+
} else {
|
|
447
|
+
error.message += " Bind an error listener, e.g. connection.on('error', (error) => ...).";
|
|
448
|
+
// oxlint-disable-next-line promise/no-promise-in-callback -- Match Realtime's explicit unhandled rejection contract when no SDK listener or completion operation observes the error.
|
|
449
|
+
Promise.reject(error);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private _onClose = (code: number): void => {
|
|
454
|
+
const reportPrematureClose =
|
|
455
|
+
!this._terminalReceived && !this._failure && !this._finishPromise && this._closeTimer === undefined;
|
|
456
|
+
this._inputClosed = true;
|
|
457
|
+
this._transportClosed = true;
|
|
458
|
+
clearTimeout(this._closeTimer);
|
|
459
|
+
this.socket.off('message', this._onMessage);
|
|
460
|
+
this.socket.off('error', this._onError);
|
|
461
|
+
this.socket.off('close', this._onClose);
|
|
462
|
+
if (!this._terminalReceived) {
|
|
463
|
+
this._failure ??= new OpenAIRealtimeError('Translation WebSocket closed before session.closed.', null);
|
|
464
|
+
} else if (code !== 1000 && code !== 1001 && code !== 1005) {
|
|
465
|
+
this._failure ??= new OpenAIRealtimeError(
|
|
466
|
+
'Translation transport closed abnormally after session.closed.',
|
|
467
|
+
null,
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
this._settleFinish();
|
|
471
|
+
if (reportPrematureClose && this._failure) {
|
|
472
|
+
this._reportError(this._failure);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
private _onAbort = (): void => {
|
|
477
|
+
const error = new OpenAIRealtimeError('Translation finish was aborted.', null);
|
|
478
|
+
Object.defineProperty(error, 'cause', {
|
|
479
|
+
value: this._signal?.reason,
|
|
480
|
+
writable: true,
|
|
481
|
+
configurable: true,
|
|
482
|
+
});
|
|
483
|
+
this._fail(error);
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
private _fail(error: OpenAIRealtimeError): void {
|
|
487
|
+
this._inputClosed = true;
|
|
488
|
+
this._failure ??= error;
|
|
489
|
+
this._settleFinish();
|
|
490
|
+
if (this.socket.readyState !== ReadyState.CLOSED) {
|
|
491
|
+
this.socket.platformSocket.terminate();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
private _settleFinish(): void {
|
|
496
|
+
if (!this._transportClosed || (!this._failure && !this._terminalDelivered)) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
clearTimeout(this._finishTimer);
|
|
500
|
+
this._signal?.removeEventListener('abort', this._onAbort);
|
|
501
|
+
this._signal = undefined;
|
|
502
|
+
if (this._failure) {
|
|
503
|
+
this._rejectFinish?.(this._failure);
|
|
504
|
+
} else {
|
|
505
|
+
this._resolveFinish?.();
|
|
506
|
+
}
|
|
507
|
+
this._resolveFinish = undefined;
|
|
508
|
+
this._rejectFinish = undefined;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
private _closeTransport(): void {
|
|
512
|
+
if (this.socket.readyState === ReadyState.CLOSED || this.socket.readyState === ReadyState.CLOSING) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (!this._finishPromise) {
|
|
516
|
+
this._closeTimer = setTimeout(() => {
|
|
517
|
+
const error = new OpenAIRealtimeError('Timed out closing the translation transport.', null);
|
|
518
|
+
this._fail(error);
|
|
519
|
+
this._reportError(error);
|
|
520
|
+
}, 1000);
|
|
521
|
+
const timer: unknown = this._closeTimer;
|
|
522
|
+
if (
|
|
523
|
+
typeof timer === 'object' &&
|
|
524
|
+
timer !== null &&
|
|
525
|
+
'unref' in timer &&
|
|
526
|
+
typeof timer.unref === 'function'
|
|
527
|
+
) {
|
|
528
|
+
timer.unref();
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
try {
|
|
532
|
+
this.socket.close(1000, 'OK');
|
|
533
|
+
} catch {
|
|
534
|
+
const error = new OpenAIRealtimeError('Could not close the translation transport.', null);
|
|
535
|
+
this._fail(error);
|
|
536
|
+
this._emit('error', error);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
@@ -26,7 +26,7 @@ export class Speech extends APIResource {
|
|
|
26
26
|
* const speech = await client.audio.speech.create({
|
|
27
27
|
* input: 'input',
|
|
28
28
|
* model: 'tts-1',
|
|
29
|
-
* voice: '
|
|
29
|
+
* voice: 'ash',
|
|
30
30
|
* });
|
|
31
31
|
*
|
|
32
32
|
* const content = await speech.blob();
|
|
@@ -82,6 +82,9 @@ export interface SpeechCreateParams {
|
|
|
82
82
|
| 'verse'
|
|
83
83
|
| 'marin'
|
|
84
84
|
| 'cedar'
|
|
85
|
+
| 'fable'
|
|
86
|
+
| 'onyx'
|
|
87
|
+
| 'nova'
|
|
85
88
|
| SpeechCreateParams.ID;
|
|
86
89
|
|
|
87
90
|
/**
|
|
@@ -24,7 +24,8 @@ export class Transcriptions extends APIResource {
|
|
|
24
24
|
* Transcribes audio into the input language.
|
|
25
25
|
*
|
|
26
26
|
* Returns a transcription object in `json`, `diarized_json`, or `verbose_json`
|
|
27
|
-
* format, or a stream of transcript
|
|
27
|
+
* format, plain text in `text`, `srt`, or `vtt` format, or a stream of transcript
|
|
28
|
+
* events. Supported formats depend on the model.
|
|
28
29
|
*
|
|
29
30
|
* @example
|
|
30
31
|
* ```ts
|