openai 7.7.0 → 7.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +15 -30
  3. package/auth/index.d.mts +1 -1
  4. package/auth/index.d.mts.map +1 -1
  5. package/auth/index.d.ts +1 -1
  6. package/auth/index.d.ts.map +1 -1
  7. package/auth/index.js.map +1 -1
  8. package/auth/index.mjs.map +1 -1
  9. package/auth/types.d.mts +9 -4
  10. package/auth/types.d.mts.map +1 -1
  11. package/auth/types.d.ts +9 -4
  12. package/auth/types.d.ts.map +1 -1
  13. package/auth/workload-identity-auth.d.mts +1 -1
  14. package/auth/workload-identity-auth.d.mts.map +1 -1
  15. package/auth/workload-identity-auth.d.ts +1 -1
  16. package/auth/workload-identity-auth.d.ts.map +1 -1
  17. package/auth/workload-identity-auth.js +23 -6
  18. package/auth/workload-identity-auth.js.map +1 -1
  19. package/auth/workload-identity-auth.mjs +23 -6
  20. package/auth/workload-identity-auth.mjs.map +1 -1
  21. package/auth/x509-transport.d.mts +36 -1
  22. package/auth/x509-transport.d.mts.map +1 -1
  23. package/auth/x509-transport.d.ts +36 -1
  24. package/auth/x509-transport.d.ts.map +1 -1
  25. package/auth/x509-transport.js +223 -0
  26. package/auth/x509-transport.js.map +1 -1
  27. package/auth/x509-transport.mjs +222 -1
  28. package/auth/x509-transport.mjs.map +1 -1
  29. package/azure.d.mts +3 -1
  30. package/azure.d.mts.map +1 -1
  31. package/azure.d.ts +3 -1
  32. package/azure.d.ts.map +1 -1
  33. package/azure.js.map +1 -1
  34. package/azure.mjs.map +1 -1
  35. package/bedrock.d.mts +3 -1
  36. package/bedrock.d.mts.map +1 -1
  37. package/bedrock.d.ts +3 -1
  38. package/bedrock.d.ts.map +1 -1
  39. package/bedrock.js.map +1 -1
  40. package/bedrock.mjs.map +1 -1
  41. package/beta/realtime/ws.d.mts.map +1 -1
  42. package/beta/realtime/ws.d.ts.map +1 -1
  43. package/beta/realtime/ws.js +2 -0
  44. package/beta/realtime/ws.js.map +1 -1
  45. package/beta/realtime/ws.mjs +2 -0
  46. package/beta/realtime/ws.mjs.map +1 -1
  47. package/client.d.mts +3 -1
  48. package/client.d.mts.map +1 -1
  49. package/client.d.ts +3 -1
  50. package/client.d.ts.map +1 -1
  51. package/client.js +30 -37
  52. package/client.js.map +1 -1
  53. package/client.mjs +31 -38
  54. package/client.mjs.map +1 -1
  55. package/core/EventEmitter.d.mts.map +1 -1
  56. package/core/EventEmitter.d.ts.map +1 -1
  57. package/core/EventEmitter.js +14 -1
  58. package/core/EventEmitter.js.map +1 -1
  59. package/core/EventEmitter.mjs +14 -1
  60. package/core/EventEmitter.mjs.map +1 -1
  61. package/internal/auth/x509-api-origin.d.mts +5 -0
  62. package/internal/auth/x509-api-origin.d.mts.map +1 -0
  63. package/internal/auth/x509-api-origin.d.ts +5 -0
  64. package/internal/auth/x509-api-origin.d.ts.map +1 -0
  65. package/internal/auth/x509-api-origin.js +28 -0
  66. package/internal/auth/x509-api-origin.js.map +1 -0
  67. package/internal/auth/x509-api-origin.mjs +24 -0
  68. package/internal/auth/x509-api-origin.mjs.map +1 -0
  69. package/internal/auth/x509-credential-options.d.mts +13 -0
  70. package/internal/auth/x509-credential-options.d.mts.map +1 -0
  71. package/internal/auth/x509-credential-options.d.ts +13 -0
  72. package/internal/auth/x509-credential-options.d.ts.map +1 -0
  73. package/internal/auth/x509-credential-options.js +122 -0
  74. package/internal/auth/x509-credential-options.js.map +1 -0
  75. package/internal/auth/x509-credential-options.mjs +118 -0
  76. package/internal/auth/x509-credential-options.mjs.map +1 -0
  77. package/internal/auth/x509-token-exchange.mjs +1 -1
  78. package/internal/auth/x509-transport-capability.d.mts +12 -5
  79. package/internal/auth/x509-transport-capability.d.mts.map +1 -1
  80. package/internal/auth/x509-transport-capability.d.ts +12 -5
  81. package/internal/auth/x509-transport-capability.d.ts.map +1 -1
  82. package/internal/auth/x509-transport-capability.js +5 -4
  83. package/internal/auth/x509-transport-capability.js.map +1 -1
  84. package/internal/auth/x509-transport-capability.mjs +6 -5
  85. package/internal/auth/x509-transport-capability.mjs.map +1 -1
  86. package/internal/auth/x509-transport-registry.d.mts +8 -0
  87. package/internal/auth/x509-transport-registry.d.mts.map +1 -1
  88. package/internal/auth/x509-transport-registry.d.ts +8 -0
  89. package/internal/auth/x509-transport-registry.d.ts.map +1 -1
  90. package/internal/auth/x509-transport-registry.js.map +1 -1
  91. package/internal/auth/x509-transport-registry.mjs +1 -1
  92. package/internal/auth/x509-transport-registry.mjs.map +1 -1
  93. package/internal/auth/x509-transport-state-browser.d.mts +4 -0
  94. package/internal/auth/x509-transport-state-browser.d.mts.map +1 -1
  95. package/internal/auth/x509-transport-state-browser.d.ts +4 -0
  96. package/internal/auth/x509-transport-state-browser.d.ts.map +1 -1
  97. package/internal/auth/x509-transport-state-browser.js +6 -1
  98. package/internal/auth/x509-transport-state-browser.js.map +1 -1
  99. package/internal/auth/x509-transport-state-browser.mjs +5 -0
  100. package/internal/auth/x509-transport-state-browser.mjs.map +1 -1
  101. package/internal/auth/x509-transport-state.cjs +6 -1
  102. package/internal/auth/x509-transport-state.cjs.map +1 -1
  103. package/internal/auth/x509-transport-state.d.cts +4 -0
  104. package/internal/auth/x509-transport-state.d.cts.map +1 -1
  105. package/internal/auth/x509-transport-state.js +36 -0
  106. package/internal/auth/x509-transport-state.mjs +19 -0
  107. package/internal/auth/x509-workload-identity-auth.d.mts +2 -4
  108. package/internal/auth/x509-workload-identity-auth.d.mts.map +1 -1
  109. package/internal/auth/x509-workload-identity-auth.d.ts +2 -4
  110. package/internal/auth/x509-workload-identity-auth.d.ts.map +1 -1
  111. package/internal/auth/x509-workload-identity-auth.js +43 -34
  112. package/internal/auth/x509-workload-identity-auth.js.map +1 -1
  113. package/internal/auth/x509-workload-identity-auth.mjs +40 -30
  114. package/internal/auth/x509-workload-identity-auth.mjs.map +1 -1
  115. package/internal/uploads.js +4 -2
  116. package/internal/uploads.js.map +1 -1
  117. package/internal/uploads.mjs +4 -2
  118. package/internal/uploads.mjs.map +1 -1
  119. package/internal/utils/log.d.mts +6 -0
  120. package/internal/utils/log.d.mts.map +1 -1
  121. package/internal/utils/log.d.ts +6 -0
  122. package/internal/utils/log.d.ts.map +1 -1
  123. package/internal/utils/log.js +72 -11
  124. package/internal/utils/log.js.map +1 -1
  125. package/internal/utils/log.mjs +69 -11
  126. package/internal/utils/log.mjs.map +1 -1
  127. package/lib/EventEmitter.d.mts.map +1 -1
  128. package/lib/EventEmitter.d.ts.map +1 -1
  129. package/lib/EventEmitter.js +14 -1
  130. package/lib/EventEmitter.js.map +1 -1
  131. package/lib/EventEmitter.mjs +14 -1
  132. package/lib/EventEmitter.mjs.map +1 -1
  133. package/package.json +5 -5
  134. package/realtime/ws.d.mts.map +1 -1
  135. package/realtime/ws.d.ts.map +1 -1
  136. package/realtime/ws.js +2 -0
  137. package/realtime/ws.js.map +1 -1
  138. package/realtime/ws.mjs +2 -0
  139. package/realtime/ws.mjs.map +1 -1
  140. package/resources/admin/organization/audit-logs.d.mts +2 -2
  141. package/resources/admin/organization/audit-logs.d.mts.map +1 -1
  142. package/resources/admin/organization/audit-logs.d.ts +2 -2
  143. package/resources/admin/organization/audit-logs.d.ts.map +1 -1
  144. package/resources/beta/responses/responses.d.mts +4 -0
  145. package/resources/beta/responses/responses.d.mts.map +1 -1
  146. package/resources/beta/responses/responses.d.ts +4 -0
  147. package/resources/beta/responses/responses.d.ts.map +1 -1
  148. package/resources/beta/responses/responses.js.map +1 -1
  149. package/resources/beta/responses/responses.mjs.map +1 -1
  150. package/resources/beta/responses/ws.d.mts.map +1 -1
  151. package/resources/beta/responses/ws.d.ts.map +1 -1
  152. package/resources/beta/responses/ws.js +2 -0
  153. package/resources/beta/responses/ws.js.map +1 -1
  154. package/resources/beta/responses/ws.mjs +2 -0
  155. package/resources/beta/responses/ws.mjs.map +1 -1
  156. package/resources/completions.d.mts +4 -0
  157. package/resources/completions.d.mts.map +1 -1
  158. package/resources/completions.d.ts +4 -0
  159. package/resources/completions.d.ts.map +1 -1
  160. package/resources/responses/responses.d.mts +4 -0
  161. package/resources/responses/responses.d.mts.map +1 -1
  162. package/resources/responses/responses.d.ts +4 -0
  163. package/resources/responses/responses.d.ts.map +1 -1
  164. package/resources/responses/responses.js.map +1 -1
  165. package/resources/responses/responses.mjs.map +1 -1
  166. package/resources/responses/ws.d.mts.map +1 -1
  167. package/resources/responses/ws.d.ts.map +1 -1
  168. package/resources/responses/ws.js +2 -0
  169. package/resources/responses/ws.js.map +1 -1
  170. package/resources/responses/ws.mjs +2 -0
  171. package/resources/responses/ws.mjs.map +1 -1
  172. package/src/_vendor/partial-json-parser/LICENSE +21 -0
  173. package/src/_vendor/partial-json-parser/README.md +11 -1
  174. package/src/auth/index.ts +1 -0
  175. package/src/auth/types.ts +10 -4
  176. package/src/auth/workload-identity-auth.ts +30 -6
  177. package/src/auth/x509-transport.ts +311 -2
  178. package/src/azure.ts +4 -1
  179. package/src/bedrock.ts +4 -1
  180. package/src/beta/realtime/ws.ts +2 -0
  181. package/src/client.ts +39 -34
  182. package/src/core/EventEmitter.ts +13 -1
  183. package/src/internal/auth/x509-api-origin.ts +27 -0
  184. package/src/internal/auth/x509-credential-options.ts +146 -0
  185. package/src/internal/auth/x509-transport-capability.ts +12 -5
  186. package/src/internal/auth/x509-transport-registry.ts +9 -0
  187. package/src/internal/auth/x509-transport-state-browser.ts +7 -0
  188. package/src/internal/auth/x509-transport-state.cts +7 -0
  189. package/src/internal/auth/x509-workload-identity-auth.ts +42 -33
  190. package/src/internal/uploads.ts +4 -2
  191. package/src/internal/utils/log.ts +76 -11
  192. package/src/lib/EventEmitter.ts +13 -1
  193. package/src/realtime/ws.ts +2 -0
  194. package/src/resources/admin/organization/audit-logs.ts +4 -0
  195. package/src/resources/beta/responses/responses.ts +5 -0
  196. package/src/resources/beta/responses/ws.ts +3 -0
  197. package/src/resources/completions.ts +5 -0
  198. package/src/resources/responses/responses.ts +5 -0
  199. package/src/resources/responses/ws.ts +3 -0
  200. package/src/version.ts +1 -1
  201. package/version.d.mts +1 -1
  202. package/version.d.ts +1 -1
  203. package/version.js +1 -1
  204. package/version.mjs +1 -1
@@ -186,11 +186,20 @@ export class EventEmitter<EventTypes extends Record<string, (...args: any) => an
186
186
  }
187
187
  return !listener.once && !listener.removed;
188
188
  }) as any;
189
+ let listenerThrew = false;
190
+ let firstListenerError: unknown;
189
191
  this.#listenerDispatchDepth += 1;
190
192
  try {
191
193
  for (const registration of listeners as any) {
192
194
  if (!registration.removed) {
193
- registration.listener(...(args as any));
195
+ try {
196
+ registration.listener(...(args as any));
197
+ } catch (error) {
198
+ if (!listenerThrew) {
199
+ listenerThrew = true;
200
+ firstListenerError = error;
201
+ }
202
+ }
194
203
  }
195
204
  }
196
205
  } finally {
@@ -199,6 +208,9 @@ export class EventEmitter<EventTypes extends Record<string, (...args: any) => an
199
208
  this.#cleanupEmittedListeners();
200
209
  }
201
210
  }
211
+ if (listenerThrew) {
212
+ throw firstListenerError;
213
+ }
202
214
  }
203
215
  }
204
216
 
@@ -0,0 +1,27 @@
1
+ import { OpenAIError } from '../../core/error';
2
+ import { isSensitiveQueryParameter } from '../utils/log';
3
+
4
+ /** Sole API authority approved for OpenAI X.509 workload-identity federation. */
5
+ export const X509_API_BASE_URL = 'https://mtls.api.openai.com/v1';
6
+
7
+ /** Validates the enrolled API authority and rejects credential-bearing query parameters. */
8
+ export function assertX509APIOrigin(value: string | URL): URL {
9
+ let target: URL;
10
+ try {
11
+ target = new URL(value);
12
+ } catch {
13
+ throw new OpenAIError('X.509 workload identity requires the approved global mTLS API origin.');
14
+ }
15
+
16
+ if (target.origin !== 'https://mtls.api.openai.com' || target.username || target.password) {
17
+ throw new OpenAIError('X.509 workload identity requires the approved global mTLS API origin.');
18
+ }
19
+ for (const name of target.searchParams.keys()) {
20
+ if (isSensitiveQueryParameter(name)) {
21
+ throw new OpenAIError(
22
+ 'X.509 workload identity cannot send conflicting query authentication credentials.',
23
+ );
24
+ }
25
+ }
26
+ return target;
27
+ }
@@ -0,0 +1,146 @@
1
+ import { OpenAIError } from '../../core/error';
2
+ import type { ClientOptions } from '../../client';
3
+ import type { X509Credential } from '../../auth/types';
4
+ import { hasOwn } from '../utils/values';
5
+ import { isX509WorkloadIdentity } from './x509-workload-identity-auth';
6
+ import type { RegisteredX509Credential } from './x509-transport-registry';
7
+ import { findX509Credential } from '#x509-transport-state';
8
+
9
+ /** Validates one privately registered credential and suppresses ambient legacy authentication. */
10
+ export function normalizeX509CredentialOptions(options: ClientOptions): {
11
+ credential: X509Credential | undefined;
12
+ options: ClientOptions;
13
+ } {
14
+ const { credential } = options;
15
+ if (credential === undefined) {
16
+ return { credential, options };
17
+ }
18
+
19
+ const registered: RegisteredX509Credential | undefined = findX509Credential(credential);
20
+ if (!registered) {
21
+ throw new OpenAIError('An X.509 credential must be created by the SDK authentication helper.');
22
+ }
23
+ const conflicting = (['apiKey', 'adminAPIKey', 'workloadIdentity', 'x509Transport'] as const).filter(
24
+ (name) => {
25
+ const value = options[name];
26
+ return value !== null && value !== undefined;
27
+ },
28
+ );
29
+ if (conflicting.length > 0) {
30
+ throw new OpenAIError(
31
+ `The \`credential\` option cannot be combined with ${conflicting.map((name) => `\`${name}\``).join(', ')}.`,
32
+ );
33
+ }
34
+
35
+ return {
36
+ credential,
37
+ options: {
38
+ ...options,
39
+ apiKey: null,
40
+ adminAPIKey: null,
41
+ baseURL: options.baseURL ?? null,
42
+ organization: options.organization ?? null,
43
+ project: options.project ?? null,
44
+ workloadIdentity: registered.identity,
45
+ x509Transport: registered.transport,
46
+ },
47
+ };
48
+ }
49
+
50
+ /** Distinguishes explicitly supplied ordinary credentials from nullish inheritance. */
51
+ function overridesOrdinaryAuthentication({ apiKey, adminAPIKey }: Partial<ClientOptions>): boolean {
52
+ return (apiKey !== null && apiKey !== undefined) || (adminAPIKey !== null && adminAPIKey !== undefined);
53
+ }
54
+
55
+ /** Keeps origin-bound configuration with its owning provider during a client clone. */
56
+ function prepareProviderClone(
57
+ inherited: ClientOptions,
58
+ overrides: Partial<ClientOptions>,
59
+ ): ClientOptions['provider'] {
60
+ const inheritedProvider = inherited.provider;
61
+ const replacingProvider = overrides.credential ?? overrides.workloadIdentity;
62
+ const provider = overrides.provider ?? (replacingProvider ? undefined : inheritedProvider);
63
+
64
+ if (provider !== inheritedProvider) {
65
+ delete inherited.baseURL;
66
+ delete inherited.organization;
67
+ delete inherited.project;
68
+ delete inherited.defaultHeaders;
69
+ delete inherited.defaultQuery;
70
+ delete inherited.fetchOptions;
71
+ delete inherited.fetch;
72
+ }
73
+ if (provider) {
74
+ delete inherited.apiKey;
75
+ delete inherited.adminAPIKey;
76
+ delete inherited.credential;
77
+ delete inherited.workloadIdentity;
78
+ delete inherited.x509Transport;
79
+ delete inherited.baseURL;
80
+ }
81
+ return provider;
82
+ }
83
+
84
+ /** Reconciles one client's credential and provider ownership before cloning its options. */
85
+ export function prepareX509ClientClone(
86
+ inherited: ClientOptions,
87
+ overrides: Partial<ClientOptions>,
88
+ credential: X509Credential | undefined,
89
+ currentlyX509: boolean,
90
+ ): { credential: X509Credential | undefined; provider: ClientOptions['provider'] } {
91
+ const nextIdentity = hasOwn(overrides, 'workloadIdentity')
92
+ ? overrides.workloadIdentity
93
+ : inherited.workloadIdentity;
94
+ const dropping =
95
+ credential !== undefined &&
96
+ ((overridesOrdinaryAuthentication(overrides) && overrides.workloadIdentity === undefined) ||
97
+ overrides.provider !== undefined);
98
+ if (credential !== undefined && hasOwn(overrides, 'workloadIdentity')) {
99
+ delete inherited.x509Transport;
100
+ }
101
+ const inheritedCredential =
102
+ credential !== undefined &&
103
+ !dropping &&
104
+ overrides.credential === undefined &&
105
+ !hasOwn(overrides, 'workloadIdentity') &&
106
+ !hasOwn(overrides, 'x509Transport')
107
+ ? credential
108
+ : undefined;
109
+ const nextCredential = overrides.credential === undefined ? inheritedCredential : overrides.credential;
110
+ const nextX509 = nextCredential !== undefined || (!dropping && isX509WorkloadIdentity(nextIdentity));
111
+
112
+ if (currentlyX509 !== nextX509) {
113
+ delete inherited.fetch;
114
+ delete inherited.baseURL;
115
+ delete inherited.organization;
116
+ delete inherited.project;
117
+ delete inherited.defaultHeaders;
118
+ delete inherited.defaultQuery;
119
+ delete inherited.fetchOptions;
120
+ if (nextX509) {
121
+ inherited.apiKey = null;
122
+ } else {
123
+ delete inherited.x509Transport;
124
+ if (dropping) {
125
+ delete inherited.workloadIdentity;
126
+ }
127
+ }
128
+ }
129
+
130
+ if (nextCredential !== undefined) {
131
+ delete inherited.apiKey;
132
+ delete inherited.adminAPIKey;
133
+ delete inherited.workloadIdentity;
134
+ delete inherited.x509Transport;
135
+ inherited.credential = nextCredential;
136
+ if (overrides.credential !== undefined) {
137
+ delete inherited.organization;
138
+ delete inherited.project;
139
+ delete inherited.defaultHeaders;
140
+ delete inherited.defaultQuery;
141
+ delete inherited.fetchOptions;
142
+ }
143
+ }
144
+
145
+ return { credential: nextCredential, provider: prepareProviderClone(inherited, overrides) };
146
+ }
@@ -15,7 +15,13 @@ export interface X509TransportOptions {
15
15
  /** X.509 transport currently supports genuine Node.js runtimes only. */
16
16
  runtime: 'node';
17
17
 
18
- /** Caller-owned Undici Agent or ProxyAgent; the SDK never closes or inspects it. */
18
+ /**
19
+ * Caller-owned Undici Agent or ProxyAgent; the SDK never closes or inspects it.
20
+ *
21
+ * The application attests that its dispatcher, factories, TLS verification,
22
+ * certificate selection, and CONNECT proxy configuration are trustworthy.
23
+ * Use `fromX509` when the SDK should own and enforce transport configuration.
24
+ */
19
25
  dispatcher: Agent | ProxyAgent;
20
26
 
21
27
  /** Attests that the dispatcher uses one static workload-certificate identity. */
@@ -150,10 +156,11 @@ function assertConnectProxySupport(): void {
150
156
  * `certificateIdentity: 'static'` is an application attestation: the SDK does
151
157
  * not inspect certificates, private dispatcher internals, callbacks, or TLS
152
158
  * options and cannot cryptographically prove certificate selection. Configure
153
- * one static identity without custom dispatcher factories. For HTTPS CONNECT,
154
- * independently configure `proxyTls` and `requestTls` so workload credentials
155
- * never reach the proxy. Rotation requires creating a fresh dispatcher and
156
- * capability; the application remains responsible for draining the old one.
159
+ * trusted dispatcher factories, verified target and proxy TLS, one static
160
+ * certificate identity, and independently scoped CONNECT credentials.
161
+ * Prefer the SDK-owned `fromX509` credential when these guarantees should be
162
+ * enforced at construction. Rotation requires a fresh caller-owned dispatcher
163
+ * and capability; the application remains responsible for draining it.
157
164
  *
158
165
  * This Node-only preview entrypoint requires the optional `undici` peer at
159
166
  * version 5.2.0 or later. CONNECT proxy modes require version 5.5.1 or
@@ -1,5 +1,6 @@
1
1
  import { OpenAIError } from '../../core/error';
2
2
  import type { NullableHeaders } from '../headers';
3
+ import type { X509WorkloadIdentity } from '../../auth/types';
3
4
  import type { ReadableStream } from '../shim-types';
4
5
  import type { MergedRequestInit } from '../types';
5
6
  import { findRegisteredX509Transport } from '#x509-transport-state';
@@ -49,6 +50,12 @@ export interface X509Transport {
49
50
  readonly [x509TransportBrand]: true;
50
51
  }
51
52
 
53
+ /** Immutable selectors and transport privately registered for an SDK-owned credential. */
54
+ export interface RegisteredX509Credential {
55
+ readonly identity: X509WorkloadIdentity;
56
+ readonly transport: X509Transport;
57
+ }
58
+
52
59
  /** Validated short-lived token exchanged using a registered certificate identity. */
53
60
  export interface X509ExchangedToken {
54
61
  /** Header-safe, in-memory OpenAI bearer credential. */
@@ -63,6 +70,8 @@ export interface X509RequestScope {
63
70
  wallStartedAt: number;
64
71
  monotonicStartedAt: number;
65
72
  deadlineArmed?: boolean;
73
+ preparationStartedAt?: number;
74
+ preparationWallStartedAt?: number;
66
75
  request?: { signal: AbortSignal | null | undefined; timeout: number; fetchOptions: MergedRequestInit };
67
76
  phase?: 'planning' | 'authorizing';
68
77
  effectiveSignal?: AbortSignal;
@@ -4,6 +4,7 @@ const transientX509ConnectionErrors = new WeakSet();
4
4
  const retryableX509IssuerErrors = new WeakSet();
5
5
  const approvedX509Clients = new WeakSet();
6
6
  const approvedX509OAuthErrors = new WeakMap();
7
+ const approvedX509Credentials = new WeakMap();
7
8
 
8
9
  /** Looks up an opaque capability without exposing the registry itself. */
9
10
  export const findRegisteredX509Transport = WeakMap.prototype.get.bind(registeredX509Transports);
@@ -34,3 +35,9 @@ export const rememberX509OAuthError = WeakMap.prototype.set.bind(approvedX509OAu
34
35
 
35
36
  /** Retrieves trusted metadata when public OAuth errors cross module formats. */
36
37
  export const findX509OAuthError = WeakMap.prototype.get.bind(approvedX509OAuthErrors);
38
+
39
+ /** Privately binds SDK-owned credentials without importing Node or optional transport peers. */
40
+ export const rememberX509Credential = WeakMap.prototype.set.bind(approvedX509Credentials);
41
+
42
+ /** Resolves only credentials registered by the optional Node authentication helper. */
43
+ export const findX509Credential = WeakMap.prototype.get.bind(approvedX509Credentials);
@@ -4,6 +4,7 @@ const transientX509ConnectionErrors = new WeakSet();
4
4
  const retryableX509IssuerErrors = new WeakSet();
5
5
  const approvedX509Clients = new WeakSet();
6
6
  const approvedX509OAuthErrors = new WeakMap();
7
+ const approvedX509Credentials = new WeakMap();
7
8
 
8
9
  /** Looks up an opaque capability without exposing the registry itself. */
9
10
  export const findRegisteredX509Transport = WeakMap.prototype.get.bind(registeredX509Transports);
@@ -34,3 +35,9 @@ export const rememberX509OAuthError = WeakMap.prototype.set.bind(approvedX509OAu
34
35
 
35
36
  /** Retrieves trusted OAuth metadata for public cross-module error normalization. */
36
37
  export const findX509OAuthError = WeakMap.prototype.get.bind(approvedX509OAuthErrors);
38
+
39
+ /** Privately binds SDK-owned credentials to their immutable identity and approved transport. */
40
+ export const rememberX509Credential = WeakMap.prototype.set.bind(approvedX509Credentials);
41
+
42
+ /** Resolves only first-class credentials created by the optional Node transport helper. */
43
+ export const findX509Credential = WeakMap.prototype.get.bind(approvedX509Credentials);
@@ -6,7 +6,9 @@ import type { HeadersLike, NullableHeaders } from '../headers';
6
6
  import type { FinalRequestOptions } from '../request-options';
7
7
  import { CancelReadableStream } from '../shims';
8
8
  import type { MergedRequestInit } from '../types';
9
+ import { isSensitiveHeader } from '../utils/log';
9
10
  import { hasOwn } from '../utils/values';
11
+ import { assertX509APIOrigin } from './x509-api-origin';
10
12
  import { resolveX509Transport } from './x509-transport-registry';
11
13
  import {
12
14
  isApprovedX509Client,
@@ -21,10 +23,6 @@ import type {
21
23
  X509Transport,
22
24
  } from './x509-transport-registry';
23
25
 
24
- /** Sole API authority approved for OpenAI X.509 workload-identity federation. */
25
- export const X509_API_BASE_URL = 'https://mtls.api.openai.com/v1';
26
-
27
- const X509_API_ORIGIN = 'https://mtls.api.openai.com';
28
26
  const FORBIDDEN_TRANSPORT_OPTIONS = ['dispatcher', 'agent', 'client', 'tls', 'proxy'];
29
27
  const headerValue = (headers: Headers, name: string): string | null =>
30
28
  Headers.prototype.get.call(headers, name);
@@ -39,12 +37,7 @@ const userAbortError = (signal: AbortSignal): APIUserAbortError => {
39
37
  function assertSafeHeaders(headers: Headers): void {
40
38
  for (const name of Headers.prototype.keys.call(headers)) {
41
39
  const canonical = name.toLowerCase().split('_').join('-');
42
- if (
43
- canonical === 'api-key' ||
44
- canonical === 'x-api-key' ||
45
- canonical === 'proxy-authorization' ||
46
- canonical === 'host'
47
- ) {
40
+ if ((canonical !== 'authorization' && isSensitiveHeader(canonical)) || canonical === 'host') {
48
41
  throw new OpenAIError('X.509 workload identity cannot send conflicting authentication credentials.');
49
42
  }
50
43
  }
@@ -180,21 +173,6 @@ export function assertX509WebSocketSupported(client: unknown): void {
180
173
  }
181
174
  }
182
175
 
183
- /** Rejects every destination outside the sole enrolled, global X.509 API authority. */
184
- export function assertX509APIOrigin(value: string | URL): URL {
185
- let target: URL;
186
- try {
187
- target = new URL(value);
188
- } catch {
189
- throw new OpenAIError('X.509 workload identity requires the approved global mTLS API origin.');
190
- }
191
-
192
- if (target.origin !== X509_API_ORIGIN || target.username || target.password) {
193
- throw new OpenAIError('X.509 workload identity requires the approved global mTLS API origin.');
194
- }
195
- return target;
196
- }
197
-
198
176
  /** Prevents caller options from replacing the immutable transport selected at construction. */
199
177
  export function assertX509FetchOptions(options: MergedRequestInit | RequestInit | undefined): void {
200
178
  if (!options) {
@@ -235,6 +213,7 @@ export class X509WorkloadIdentityAuth {
235
213
  readonly #identityProviderId: string;
236
214
  readonly #serviceAccountId: string;
237
215
  readonly #configuredRefreshBufferMs: number | undefined;
216
+ readonly #configuredRefreshBufferSeconds: number | undefined;
238
217
  readonly #organization: string | null;
239
218
  readonly #project: string | null;
240
219
  readonly #transport: RegisteredX509Transport;
@@ -254,15 +233,32 @@ export class X509WorkloadIdentityAuth {
254
233
  this.#identityProviderId = identity.identityProviderId;
255
234
  this.#serviceAccountId = identity.serviceAccountId;
256
235
  this.#configuredRefreshBufferMs = identity.refreshBufferMs;
236
+ this.#configuredRefreshBufferSeconds = identity.refreshBufferSeconds;
257
237
  this.#organization = organization;
258
238
  this.#project = project;
239
+ if (this.#configuredRefreshBufferMs !== undefined && this.#configuredRefreshBufferSeconds !== undefined) {
240
+ throw new OpenAIError(
241
+ 'X.509 workload identity cannot combine refreshBufferSeconds and refreshBufferMs.',
242
+ );
243
+ }
259
244
  if (
260
245
  this.#configuredRefreshBufferMs !== undefined &&
261
246
  (!Number.isSafeInteger(this.#configuredRefreshBufferMs) || this.#configuredRefreshBufferMs < 0)
262
247
  ) {
263
248
  throw new OpenAIError('X.509 workload identity requires a nonnegative integer refreshBufferMs.');
264
249
  }
265
- this.#refreshBufferMs = this.#configuredRefreshBufferMs ?? DEFAULT_REFRESH_BUFFER_MS;
250
+ if (
251
+ this.#configuredRefreshBufferSeconds !== undefined &&
252
+ (!Number.isSafeInteger(this.#configuredRefreshBufferSeconds) ||
253
+ this.#configuredRefreshBufferSeconds < 0 ||
254
+ !Number.isSafeInteger(this.#configuredRefreshBufferSeconds * 1000))
255
+ ) {
256
+ throw new OpenAIError('X.509 workload identity requires a nonnegative integer refreshBufferSeconds.');
257
+ }
258
+ this.#refreshBufferMs =
259
+ this.#configuredRefreshBufferSeconds === undefined
260
+ ? (this.#configuredRefreshBufferMs ?? DEFAULT_REFRESH_BUFFER_MS)
261
+ : this.#configuredRefreshBufferSeconds * 1000;
266
262
  }
267
263
 
268
264
  /** Reconstructs the immutable selectors captured before caller-owned identity mutation. */
@@ -274,6 +270,9 @@ export class X509WorkloadIdentityAuth {
274
270
  ...(this.#configuredRefreshBufferMs === undefined
275
271
  ? {}
276
272
  : { refreshBufferMs: this.#configuredRefreshBufferMs }),
273
+ ...(this.#configuredRefreshBufferSeconds === undefined
274
+ ? {}
275
+ : { refreshBufferSeconds: this.#configuredRefreshBufferSeconds }),
277
276
  };
278
277
  }
279
278
 
@@ -365,6 +364,15 @@ export class X509WorkloadIdentityAuth {
365
364
  return request;
366
365
  }
367
366
 
367
+ /** Suspends an already-running network budget during retry-local asynchronous preparation. */
368
+ beginRequestPreparation(): void {
369
+ const scope = this.#scope();
370
+ if (scope.deadlineArmed && scope.preparationStartedAt === undefined) {
371
+ scope.preparationStartedAt = performance.now();
372
+ scope.preparationWallStartedAt = Date.now();
373
+ }
374
+ }
375
+
368
376
  /** Begins local request construction without charging protected hook latency to the network. */
369
377
  beginRequestPlanning(): void {
370
378
  this.#scope().phase = 'planning';
@@ -377,6 +385,11 @@ export class X509WorkloadIdentityAuth {
377
385
  scope.wallStartedAt = Date.now();
378
386
  scope.monotonicStartedAt = performance.now();
379
387
  scope.deadlineArmed = true;
388
+ } else if (scope.preparationStartedAt !== undefined) {
389
+ scope.monotonicStartedAt += performance.now() - scope.preparationStartedAt;
390
+ scope.wallStartedAt += Date.now() - (scope.preparationWallStartedAt ?? Date.now());
391
+ delete scope.preparationStartedAt;
392
+ delete scope.preparationWallStartedAt;
380
393
  }
381
394
  }
382
395
 
@@ -550,6 +563,8 @@ export class X509WorkloadIdentityAuth {
550
563
  delete scope.request;
551
564
  delete scope.phase;
552
565
  delete scope.deadlineArmed;
566
+ delete scope.preparationStartedAt;
567
+ delete scope.preparationWallStartedAt;
553
568
  delete scope.effectiveSignal;
554
569
  delete scope.materializedBody;
555
570
  delete scope.apiURL;
@@ -820,13 +835,7 @@ export class X509WorkloadIdentityAuth {
820
835
  'X.509 workload identity cannot override its enrolled organization or project.',
821
836
  );
822
837
  }
823
- if (
824
- canonical === 'authorization' ||
825
- canonical === 'api-key' ||
826
- canonical === 'x-api-key' ||
827
- canonical === 'proxy-authorization' ||
828
- canonical === 'host'
829
- ) {
838
+ if (isSensitiveHeader(canonical) || canonical === 'host') {
830
839
  throw new OpenAIError(
831
840
  'X.509 workload identity cannot use caller-supplied authentication credentials.',
832
841
  );
@@ -348,7 +348,8 @@ const hasStreamingUploadableValue = (value: unknown): boolean => {
348
348
  return value.some(hasStreamingUploadableValue);
349
349
  }
350
350
  if (value && typeof value === 'object' && !isBlob(value) && !(value instanceof Response)) {
351
- for (const k in value) {
351
+ // Own properties only, matching what form encoding serializes; inherited values are never encoded.
352
+ for (const k of Object.keys(value)) {
352
353
  if (hasStreamingUploadableValue((value as Record<string, unknown>)[k])) {
353
354
  return true;
354
355
  }
@@ -365,7 +366,8 @@ const hasUploadableValue = (value: unknown): boolean => {
365
366
  return value.some(hasUploadableValue);
366
367
  }
367
368
  if (value && typeof value === 'object') {
368
- for (const k in value) {
369
+ // Own properties only, matching what form encoding serializes; inherited values are never encoded.
370
+ for (const k of Object.keys(value)) {
369
371
  if (hasUploadableValue((value as any)[k])) {
370
372
  return true;
371
373
  }
@@ -82,6 +82,62 @@ export function loggerFor(client: OpenAI): Logger {
82
82
  return levelLogger;
83
83
  }
84
84
 
85
+ const sensitiveQueryNames = new Set([
86
+ 'apikey',
87
+ 'accesstoken',
88
+ 'refreshtoken',
89
+ 'sessiontoken',
90
+ 'sessionid',
91
+ 'idtoken',
92
+ 'authtoken',
93
+ 'authorization',
94
+ 'token',
95
+ 'password',
96
+ 'clientsecret',
97
+ 'xamzsecuritytoken',
98
+ 'xamzsignature',
99
+ 'xamzcredential',
100
+ ]);
101
+
102
+ /** Recognizes credential-bearing query names across ordinary and provider authentication. */
103
+ export function isSensitiveQueryParameter(name: string): boolean {
104
+ const normalized = name.toLowerCase().replace(/[-_]/gu, '');
105
+ return sensitiveQueryNames.has(normalized) || sensitiveQueryNames.has(normalized.replace(/^x/u, ''));
106
+ }
107
+
108
+ const sensitiveHeaderNames = new Set([
109
+ 'authorization',
110
+ 'proxy-authorization',
111
+ 'api-key',
112
+ 'x-api-key',
113
+ 'x-amz-security-token',
114
+ 'cookie',
115
+ 'set-cookie',
116
+ 'x-session-token',
117
+ 'x-session-id',
118
+ 'x-auth-token',
119
+ 'x-id-token',
120
+ ]);
121
+
122
+ /** Recognizes credential-bearing request headers across provider and workload authentication. */
123
+ export function isSensitiveHeader(name: string): boolean {
124
+ return sensitiveHeaderNames.has(name.toLowerCase().replace(/_/gu, '-')) || isSensitiveQueryParameter(name);
125
+ }
126
+
127
+ /** Removes credential-valued query parameters before a request URL reaches any logger. */
128
+ export function redactURL(value: string): string {
129
+ const url = new URL(value);
130
+ url.username = '';
131
+ url.password = '';
132
+ url.hash = '';
133
+ for (const name of url.searchParams.keys()) {
134
+ if (isSensitiveQueryParameter(name)) {
135
+ url.searchParams.set(name, '***');
136
+ }
137
+ }
138
+ return url.href;
139
+ }
140
+
85
141
  export const formatRequestDetails = (details: {
86
142
  options?: RequestOptions | undefined;
87
143
  headers?: Headers | Record<string, string> | undefined;
@@ -97,21 +153,30 @@ export const formatRequestDetails = (details: {
97
153
  if (details.options) {
98
154
  details.options = { ...details.options };
99
155
  delete details.options['headers']; // redundant + leaks internals
156
+ if (details.options.path) {
157
+ const path = details.options.path;
158
+ const redacted = new URL(redactURL(new URL(path, 'https://redacted.invalid').href));
159
+ details.options.path =
160
+ redacted.origin === 'https://redacted.invalid'
161
+ ? `${path.startsWith('/') ? '/' : ''}${redacted.pathname.slice(1)}${redacted.search}`
162
+ : redacted.href;
163
+ }
164
+ if (details.options.query) {
165
+ details.options.query = Object.fromEntries(
166
+ Object.entries(details.options.query).map(([name, value]) => [
167
+ name,
168
+ isSensitiveQueryParameter(name) ? '***' : value,
169
+ ]),
170
+ );
171
+ }
172
+ }
173
+ if (details.url) {
174
+ details.url = redactURL(details.url);
100
175
  }
101
176
  if (details.headers) {
102
177
  details.headers = Object.fromEntries(
103
178
  (details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map(
104
- ([name, value]) => [
105
- name,
106
- name.toLowerCase() === 'authorization' ||
107
- name.toLowerCase() === 'api-key' ||
108
- name.toLowerCase() === 'x-api-key' ||
109
- name.toLowerCase() === 'x-amz-security-token' ||
110
- name.toLowerCase() === 'cookie' ||
111
- name.toLowerCase() === 'set-cookie'
112
- ? '***'
113
- : value,
114
- ],
179
+ ([name, value]) => [name, isSensitiveHeader(name) ? '***' : value],
115
180
  ),
116
181
  );
117
182
  }
@@ -184,11 +184,20 @@ export class EventEmitter<EventTypes extends Record<string, (...args: any) => an
184
184
  }
185
185
  return !listener.once && !listener.removed;
186
186
  }) as any;
187
+ let listenerThrew = false;
188
+ let firstListenerError: unknown;
187
189
  this.#listenerDispatchDepth += 1;
188
190
  try {
189
191
  for (const registration of listeners as any) {
190
192
  if (!registration.removed) {
191
- registration.listener(...(args as any));
193
+ try {
194
+ registration.listener(...(args as any));
195
+ } catch (error) {
196
+ if (!listenerThrew) {
197
+ listenerThrew = true;
198
+ firstListenerError = error;
199
+ }
200
+ }
192
201
  }
193
202
  }
194
203
  } finally {
@@ -197,6 +206,9 @@ export class EventEmitter<EventTypes extends Record<string, (...args: any) => an
197
206
  this.#cleanupEmittedListeners();
198
207
  }
199
208
  }
209
+ if (listenerThrew) {
210
+ throw firstListenerError;
211
+ }
200
212
  }
201
213
  }
202
214
 
@@ -3,6 +3,7 @@ import { assertBedrockWebSocketOrigin } from '../internal/bedrock';
3
3
  import { protectWebSocketOptionsFromCredentialRedirects } from '../internal/ws';
4
4
  import type { AzureOpenAI } from '../index';
5
5
  import { OpenAI } from '../index';
6
+ import { VERSION } from '../version';
6
7
  import type { RealtimeClientEvent } from '../resources/realtime/realtime';
7
8
  import {
8
9
  OpenAIRealtimeEmitter,
@@ -62,6 +63,7 @@ export class OpenAIRealtimeWS extends OpenAIRealtimeEmitter {
62
63
  this.url = buildRealtimeURL(client, props);
63
64
  assertBedrockWebSocketOrigin(client, this.url);
64
65
  const headers = {
66
+ 'User-Agent': `${client.constructor.name}/JS ${VERSION}`,
65
67
  ...props.options?.headers,
66
68
  ...(isAzure(client) && !props.__resolvedApiKey ? {} : { Authorization: `Bearer ${client.apiKey}` }),
67
69
  };
@@ -130,6 +130,8 @@ export interface AuditLogListResponse {
130
130
  | 'tenant.admin_api_key.updated'
131
131
  | 'tenant.admin_api_key.deleted'
132
132
  | 'tenant.project_api_key.created'
133
+ | 'tenant.trusted_access.business_verification.started'
134
+ | 'tenant.trusted_access.application.submitted'
133
135
  | 'tenant.chatgpt_access_token.revoked'
134
136
  | 'tenant.migration.completed'
135
137
  | 'tenant.sso.migrated'
@@ -1936,6 +1938,8 @@ export interface AuditLogListParams extends ConversationCursorPageParams {
1936
1938
  | 'tenant.admin_api_key.updated'
1937
1939
  | 'tenant.admin_api_key.deleted'
1938
1940
  | 'tenant.project_api_key.created'
1941
+ | 'tenant.trusted_access.business_verification.started'
1942
+ | 'tenant.trusted_access.application.submitted'
1939
1943
  | 'tenant.chatgpt_access_token.revoked'
1940
1944
  | 'tenant.migration.completed'
1941
1945
  | 'tenant.sso.migrated'
@@ -10525,6 +10525,11 @@ export interface BetaResponseUsage {
10525
10525
  * The total number of tokens used.
10526
10526
  */
10527
10527
  total_tokens: number;
10528
+
10529
+ /**
10530
+ * Compute units for the request. Currently null when available.
10531
+ */
10532
+ compute_units?: number | null;
10528
10533
  }
10529
10534
 
10530
10535
  export namespace BetaResponseUsage {