browser-use 0.6.1 → 0.7.1

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 (84) hide show
  1. package/README.md +24 -18
  2. package/dist/actor/element.js +24 -3
  3. package/dist/actor/mouse.js +21 -3
  4. package/dist/actor/page.js +33 -11
  5. package/dist/agent/gif.js +28 -3
  6. package/dist/agent/message-manager/service.js +2 -22
  7. package/dist/agent/message-manager/utils.js +15 -2
  8. package/dist/agent/message-manager/views.d.ts +7 -7
  9. package/dist/agent/message-manager/views.js +1 -0
  10. package/dist/agent/prompts.d.ts +3 -0
  11. package/dist/agent/prompts.js +22 -12
  12. package/dist/agent/service.d.ts +9 -1
  13. package/dist/agent/service.js +204 -79
  14. package/dist/agent/system_prompt.md +12 -11
  15. package/dist/agent/system_prompt_anthropic_flash.md +6 -5
  16. package/dist/agent/system_prompt_no_thinking.md +12 -11
  17. package/dist/agent/views.d.ts +2 -0
  18. package/dist/agent/views.js +48 -36
  19. package/dist/browser/extensions.js +20 -10
  20. package/dist/browser/profile.d.ts +4 -0
  21. package/dist/browser/profile.js +107 -4
  22. package/dist/browser/session.d.ts +28 -1
  23. package/dist/browser/session.js +1436 -528
  24. package/dist/browser/watchdogs/default-action-watchdog.js +32 -3
  25. package/dist/browser/watchdogs/downloads-watchdog.d.ts +4 -0
  26. package/dist/browser/watchdogs/downloads-watchdog.js +105 -9
  27. package/dist/browser/watchdogs/har-recording-watchdog.d.ts +1 -0
  28. package/dist/browser/watchdogs/har-recording-watchdog.js +54 -2
  29. package/dist/browser/watchdogs/permissions-watchdog.d.ts +5 -0
  30. package/dist/browser/watchdogs/permissions-watchdog.js +106 -3
  31. package/dist/browser/watchdogs/recording-watchdog.d.ts +2 -0
  32. package/dist/browser/watchdogs/recording-watchdog.js +54 -2
  33. package/dist/browser/watchdogs/security-watchdog.d.ts +1 -0
  34. package/dist/browser/watchdogs/security-watchdog.js +47 -7
  35. package/dist/browser/watchdogs/storage-state-watchdog.d.ts +6 -0
  36. package/dist/browser/watchdogs/storage-state-watchdog.js +206 -14
  37. package/dist/cli.d.ts +13 -2
  38. package/dist/cli.js +190 -9
  39. package/dist/code-use/namespace.js +52 -7
  40. package/dist/code-use/notebook-export.js +18 -2
  41. package/dist/code-use/service.js +1 -0
  42. package/dist/config.js +26 -4
  43. package/dist/controller/action-timeout.d.ts +9 -0
  44. package/dist/controller/action-timeout.js +95 -0
  45. package/dist/controller/registry/service.d.ts +1 -0
  46. package/dist/controller/registry/service.js +28 -1
  47. package/dist/controller/service.d.ts +2 -1
  48. package/dist/controller/service.js +494 -329
  49. package/dist/entrypoint.d.ts +1 -0
  50. package/dist/entrypoint.js +27 -0
  51. package/dist/filesystem/file-system.js +38 -8
  52. package/dist/integrations/gmail/service.js +30 -6
  53. package/dist/llm/browser-use/chat.js +2 -2
  54. package/dist/llm/codex/auth.d.ts +118 -0
  55. package/dist/llm/codex/auth.js +599 -0
  56. package/dist/llm/codex/chat.d.ts +70 -0
  57. package/dist/llm/codex/chat.js +392 -0
  58. package/dist/llm/codex/index.d.ts +2 -0
  59. package/dist/llm/codex/index.js +2 -0
  60. package/dist/llm/google/chat.js +18 -1
  61. package/dist/logging-config.js +22 -11
  62. package/dist/mcp/client.d.ts +1 -0
  63. package/dist/mcp/client.js +12 -10
  64. package/dist/mcp/redaction.d.ts +3 -0
  65. package/dist/mcp/redaction.js +132 -0
  66. package/dist/mcp/server.d.ts +2 -0
  67. package/dist/mcp/server.js +64 -22
  68. package/dist/screenshots/service.js +25 -2
  69. package/dist/skill-cli/direct.d.ts +4 -1
  70. package/dist/skill-cli/direct.js +263 -66
  71. package/dist/skill-cli/server.d.ts +1 -0
  72. package/dist/skill-cli/server.js +115 -25
  73. package/dist/skill-cli/tunnel.d.ts +1 -0
  74. package/dist/skill-cli/tunnel.js +16 -4
  75. package/dist/sync/auth.js +22 -9
  76. package/dist/telemetry/service.js +21 -2
  77. package/dist/telemetry/views.js +31 -8
  78. package/dist/tokens/custom-pricing.js +2 -2
  79. package/dist/tokens/openrouter-pricing.d.ts +11 -0
  80. package/dist/tokens/openrouter-pricing.js +102 -0
  81. package/dist/tokens/service.js +20 -16
  82. package/dist/utils.d.ts +3 -1
  83. package/dist/utils.js +3 -1
  84. package/package.json +68 -27
@@ -0,0 +1,599 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { CONFIG } from '../../config.js';
6
+ export const CODEX_PROVIDER = 'openai-codex';
7
+ export const DEFAULT_CODEX_BASE_URL = 'https://chatgpt.com/backend-api/codex';
8
+ export const CODEX_OAUTH_CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
9
+ export const CODEX_OAUTH_TOKEN_URL = 'https://auth.openai.com/oauth/token';
10
+ export const CODEX_DEVICE_AUTH_BASE_URL = 'https://auth.openai.com';
11
+ export const CODEX_ACCESS_TOKEN_REFRESH_SKEW_SECONDS = 120;
12
+ const AUTH_STORE_VERSION = 1;
13
+ const DEFAULT_LOCK_TIMEOUT_MS = 20_000;
14
+ export class CodexAuthError extends Error {
15
+ provider = CODEX_PROVIDER;
16
+ code;
17
+ relogin_required;
18
+ constructor(message, code = 'codex_auth_error', reloginRequired = false) {
19
+ super(message);
20
+ this.name = 'CodexAuthError';
21
+ this.code = code;
22
+ this.relogin_required = reloginRequired;
23
+ }
24
+ }
25
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
26
+ const nowIso = () => new Date().toISOString().replace('+00:00', 'Z');
27
+ const expandHome = (value) => {
28
+ if (value === '~') {
29
+ return os.homedir();
30
+ }
31
+ if (value.startsWith('~/') || value.startsWith('~\\')) {
32
+ return path.join(os.homedir(), value.slice(2));
33
+ }
34
+ return value;
35
+ };
36
+ const resolveBaseConfigDir = (configDir) => configDir
37
+ ? path.resolve(expandHome(configDir))
38
+ : (CONFIG.BROWSER_USE_CONFIG_DIR ??
39
+ path.join(os.homedir(), '.config', 'browseruse'));
40
+ export const getCodexAuthStorePath = (options = {}) => options.authStorePath
41
+ ? path.resolve(expandHome(options.authStorePath))
42
+ : path.join(resolveBaseConfigDir(options.configDir), 'auth.json');
43
+ const chmodPrivatePath = async (targetPath, mode) => {
44
+ if (process.platform === 'win32') {
45
+ return;
46
+ }
47
+ try {
48
+ await fs.chmod(targetPath, mode);
49
+ }
50
+ catch {
51
+ /* best effort */
52
+ }
53
+ };
54
+ const ensurePrivateDirectory = async (dirPath) => {
55
+ await fs.mkdir(dirPath, { recursive: true, mode: 0o700 });
56
+ await chmodPrivatePath(dirPath, 0o700);
57
+ };
58
+ const normalizeStore = (store) => ({
59
+ version: AUTH_STORE_VERSION,
60
+ ...store,
61
+ providers: store.providers && typeof store.providers === 'object'
62
+ ? store.providers
63
+ : {},
64
+ });
65
+ const readStore = async (authStorePath) => {
66
+ try {
67
+ const raw = await fs.readFile(authStorePath, 'utf-8');
68
+ const parsed = JSON.parse(raw);
69
+ if (!parsed || typeof parsed !== 'object') {
70
+ return normalizeStore({});
71
+ }
72
+ return normalizeStore(parsed);
73
+ }
74
+ catch (error) {
75
+ const nodeError = error;
76
+ if (nodeError.code === 'ENOENT') {
77
+ return normalizeStore({});
78
+ }
79
+ throw error;
80
+ }
81
+ };
82
+ const writeStore = async (authStorePath, store) => {
83
+ await ensurePrivateDirectory(path.dirname(authStorePath));
84
+ const tmpPath = `${authStorePath}.${process.pid}.${randomUUID()}.tmp`;
85
+ await fs.writeFile(tmpPath, JSON.stringify(normalizeStore(store), null, 2), {
86
+ encoding: 'utf-8',
87
+ mode: 0o600,
88
+ });
89
+ await chmodPrivatePath(tmpPath, 0o600);
90
+ await fs.rename(tmpPath, authStorePath);
91
+ await chmodPrivatePath(authStorePath, 0o600);
92
+ };
93
+ const waitForLockRetry = async (start, timeoutMs) => {
94
+ if (Date.now() - start >= timeoutMs) {
95
+ throw new CodexAuthError('Timed out waiting for the Codex auth store lock.', 'codex_auth_lock_timeout');
96
+ }
97
+ await sleep(50);
98
+ };
99
+ const withAuthStoreLock = async (authStorePath, callback, timeoutMs = DEFAULT_LOCK_TIMEOUT_MS) => {
100
+ const lockPath = `${authStorePath}.lock`;
101
+ const lockDir = path.dirname(lockPath);
102
+ await ensurePrivateDirectory(lockDir);
103
+ const start = Date.now();
104
+ const staleMs = Math.max(timeoutMs * 2, 30_000);
105
+ let handle = null;
106
+ while (!handle) {
107
+ try {
108
+ handle = await fs.open(lockPath, 'wx', 0o600);
109
+ await handle.writeFile(JSON.stringify({
110
+ pid: process.pid,
111
+ created_at: nowIso(),
112
+ }));
113
+ await handle.close();
114
+ handle = null;
115
+ break;
116
+ }
117
+ catch (error) {
118
+ const nodeError = error;
119
+ if (nodeError.code !== 'EEXIST') {
120
+ throw error;
121
+ }
122
+ try {
123
+ const stat = await fs.stat(lockPath);
124
+ if (Date.now() - stat.mtimeMs > staleMs) {
125
+ await fs.unlink(lockPath);
126
+ continue;
127
+ }
128
+ }
129
+ catch (statError) {
130
+ const statNodeError = statError;
131
+ if (statNodeError.code === 'ENOENT') {
132
+ continue;
133
+ }
134
+ throw statError;
135
+ }
136
+ await waitForLockRetry(start, timeoutMs);
137
+ }
138
+ }
139
+ try {
140
+ return await callback();
141
+ }
142
+ finally {
143
+ try {
144
+ await fs.unlink(lockPath);
145
+ }
146
+ catch {
147
+ /* best effort */
148
+ }
149
+ }
150
+ };
151
+ const requireTokenString = (value, code, message) => {
152
+ if (typeof value !== 'string' || !value.trim()) {
153
+ throw new CodexAuthError(message, code, true);
154
+ }
155
+ return value.trim();
156
+ };
157
+ export const readCodexTokens = async (options = {}) => {
158
+ const authStorePath = getCodexAuthStorePath(options);
159
+ const store = await readStore(authStorePath);
160
+ const state = store.providers?.[CODEX_PROVIDER];
161
+ if (!state) {
162
+ throw new CodexAuthError('No Codex credentials stored. Run `browser-use auth codex login` to authenticate.', 'codex_auth_missing', true);
163
+ }
164
+ if (!state.tokens || typeof state.tokens !== 'object') {
165
+ throw new CodexAuthError('Codex auth state is missing tokens. Run `browser-use auth codex login` to re-authenticate.', 'codex_auth_invalid_shape', true);
166
+ }
167
+ const accessToken = requireTokenString(state.tokens.access_token, 'codex_auth_missing_access_token', 'Codex auth is missing access_token. Run `browser-use auth codex login` to re-authenticate.');
168
+ const refreshToken = requireTokenString(state.tokens.refresh_token, 'codex_auth_missing_refresh_token', 'Codex auth is missing refresh_token. Run `browser-use auth codex login` to re-authenticate.');
169
+ return {
170
+ tokens: {
171
+ ...state.tokens,
172
+ access_token: accessToken,
173
+ refresh_token: refreshToken,
174
+ },
175
+ last_refresh: typeof state.last_refresh === 'string' ? state.last_refresh : null,
176
+ auth_mode: typeof state.auth_mode === 'string' ? state.auth_mode : null,
177
+ source: typeof state.source === 'string' ? state.source : null,
178
+ };
179
+ };
180
+ export const saveCodexTokens = async (tokens, options = {}) => {
181
+ const accessToken = requireTokenString(tokens.access_token, 'codex_auth_missing_access_token', 'Cannot save Codex auth without access_token.');
182
+ const refreshToken = requireTokenString(tokens.refresh_token, 'codex_auth_missing_refresh_token', 'Cannot save Codex auth without refresh_token.');
183
+ const authStorePath = getCodexAuthStorePath(options);
184
+ await withAuthStoreLock(authStorePath, async () => {
185
+ const store = await readStore(authStorePath);
186
+ const providers = store.providers ?? {};
187
+ providers[CODEX_PROVIDER] = {
188
+ ...(providers[CODEX_PROVIDER] ?? {}),
189
+ tokens: {
190
+ ...tokens,
191
+ access_token: accessToken,
192
+ refresh_token: refreshToken,
193
+ },
194
+ last_refresh: options.lastRefresh ?? nowIso(),
195
+ auth_mode: 'chatgpt',
196
+ source: options.source ?? 'browser-use-auth-store',
197
+ };
198
+ await writeStore(authStorePath, {
199
+ ...store,
200
+ version: AUTH_STORE_VERSION,
201
+ active_provider: CODEX_PROVIDER,
202
+ providers,
203
+ });
204
+ }, options.lockTimeoutMs);
205
+ };
206
+ export const clearCodexTokens = async (options = {}) => {
207
+ const authStorePath = getCodexAuthStorePath(options);
208
+ await withAuthStoreLock(authStorePath, async () => {
209
+ const store = await readStore(authStorePath);
210
+ const providers = { ...(store.providers ?? {}) };
211
+ delete providers[CODEX_PROVIDER];
212
+ await writeStore(authStorePath, {
213
+ ...store,
214
+ active_provider: store.active_provider === CODEX_PROVIDER
215
+ ? undefined
216
+ : store.active_provider,
217
+ providers,
218
+ });
219
+ }, options.lockTimeoutMs);
220
+ };
221
+ const decodeJwtPayload = (token) => {
222
+ const parts = token.split('.');
223
+ if (parts.length < 2 || !parts[1]) {
224
+ return null;
225
+ }
226
+ try {
227
+ const padded = parts[1] + '='.repeat((4 - (parts[1].length % 4)) % 4);
228
+ return JSON.parse(Buffer.from(padded, 'base64url').toString('utf-8'));
229
+ }
230
+ catch {
231
+ return null;
232
+ }
233
+ };
234
+ export const codexAccessTokenIsExpiring = (accessToken, skewSeconds = CODEX_ACCESS_TOKEN_REFRESH_SKEW_SECONDS, nowMs = Date.now()) => {
235
+ if (!accessToken.trim()) {
236
+ return true;
237
+ }
238
+ const claims = decodeJwtPayload(accessToken);
239
+ if (typeof claims?.exp !== 'number') {
240
+ return false;
241
+ }
242
+ return claims.exp * 1000 <= nowMs + skewSeconds * 1000;
243
+ };
244
+ export const getCodexCloudflareHeaders = (accessToken) => {
245
+ const headers = {
246
+ 'User-Agent': 'codex_cli_rs/0.0.0 (browser-use)',
247
+ originator: 'codex_cli_rs',
248
+ };
249
+ const claims = decodeJwtPayload(accessToken);
250
+ const accountId = claims?.['https://api.openai.com/auth']?.chatgpt_account_id;
251
+ if (typeof accountId === 'string' && accountId.trim()) {
252
+ headers['ChatGPT-Account-ID'] = accountId.trim();
253
+ }
254
+ return headers;
255
+ };
256
+ export const importCodexCliTokens = async (options = {}) => {
257
+ const codexHome = options.codexHome ??
258
+ process.env.CODEX_HOME?.trim() ??
259
+ path.join(os.homedir(), '.codex');
260
+ const authPath = options.authPath
261
+ ? path.resolve(expandHome(options.authPath))
262
+ : path.join(path.resolve(expandHome(codexHome)), 'auth.json');
263
+ try {
264
+ const raw = await fs.readFile(authPath, 'utf-8');
265
+ const parsed = JSON.parse(raw);
266
+ const tokens = parsed?.tokens;
267
+ if (!tokens || typeof tokens !== 'object') {
268
+ return null;
269
+ }
270
+ const accessToken = tokens.access_token;
271
+ const refreshToken = tokens.refresh_token;
272
+ if (typeof accessToken !== 'string' ||
273
+ !accessToken.trim() ||
274
+ typeof refreshToken !== 'string' ||
275
+ !refreshToken.trim()) {
276
+ return null;
277
+ }
278
+ if (codexAccessTokenIsExpiring(accessToken, 0, options.nowMs)) {
279
+ return null;
280
+ }
281
+ return {
282
+ ...tokens,
283
+ access_token: accessToken.trim(),
284
+ refresh_token: refreshToken.trim(),
285
+ };
286
+ }
287
+ catch {
288
+ return null;
289
+ }
290
+ };
291
+ const fetchWithTimeout = async (url, init, options = {}) => {
292
+ const fetchImplementation = options.fetchImplementation ?? fetch;
293
+ const timeoutMs = options.timeoutMs ?? 20_000;
294
+ const controller = new AbortController();
295
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
296
+ try {
297
+ return await fetchImplementation(url, {
298
+ ...init,
299
+ signal: init.signal ?? controller.signal,
300
+ });
301
+ }
302
+ finally {
303
+ clearTimeout(timeout);
304
+ }
305
+ };
306
+ const parseErrorPayload = async (response) => {
307
+ let code = 'codex_refresh_failed';
308
+ let message = `Codex token refresh failed with status ${response.status}.`;
309
+ let reloginRequired = false;
310
+ try {
311
+ const payload = (await response.json());
312
+ const error = payload?.error;
313
+ if (error && typeof error === 'object') {
314
+ const nestedCode = error.code ?? error.type;
315
+ if (typeof nestedCode === 'string' && nestedCode.trim()) {
316
+ code = nestedCode.trim();
317
+ }
318
+ if (typeof error.message === 'string' && error.message.trim()) {
319
+ message = `Codex token refresh failed: ${error.message.trim()}`;
320
+ }
321
+ }
322
+ else if (typeof error === 'string' && error.trim()) {
323
+ code = error.trim();
324
+ const description = payload.error_description ?? payload.message;
325
+ if (typeof description === 'string' && description.trim()) {
326
+ message = `Codex token refresh failed: ${description.trim()}`;
327
+ }
328
+ }
329
+ }
330
+ catch {
331
+ /* keep generic message */
332
+ }
333
+ if (['invalid_grant', 'invalid_token', 'invalid_request'].includes(code)) {
334
+ reloginRequired = true;
335
+ }
336
+ if (code === 'refresh_token_reused') {
337
+ message =
338
+ 'Codex refresh token was already consumed by another client. Run `browser-use auth codex login --force` to create a fresh browser-use session.';
339
+ reloginRequired = true;
340
+ }
341
+ if ((response.status === 401 || response.status === 403) &&
342
+ !reloginRequired) {
343
+ reloginRequired = true;
344
+ }
345
+ return { code, message, reloginRequired };
346
+ };
347
+ export const refreshCodexOAuth = async (accessToken, refreshToken, options = {}) => {
348
+ void accessToken;
349
+ const cleanRefreshToken = requireTokenString(refreshToken, 'codex_auth_missing_refresh_token', 'Codex auth is missing refresh_token. Run `browser-use auth codex login` to re-authenticate.');
350
+ const response = await fetchWithTimeout(options.tokenUrl ?? CODEX_OAUTH_TOKEN_URL, {
351
+ method: 'POST',
352
+ headers: {
353
+ Accept: 'application/json',
354
+ 'Content-Type': 'application/x-www-form-urlencoded',
355
+ },
356
+ body: new URLSearchParams({
357
+ grant_type: 'refresh_token',
358
+ refresh_token: cleanRefreshToken,
359
+ client_id: options.clientId ?? CODEX_OAUTH_CLIENT_ID,
360
+ }),
361
+ }, options);
362
+ if (!response.ok) {
363
+ const parsed = await parseErrorPayload(response);
364
+ throw new CodexAuthError(parsed.message, parsed.code, parsed.reloginRequired);
365
+ }
366
+ let payload;
367
+ try {
368
+ payload = await response.json();
369
+ }
370
+ catch (error) {
371
+ throw new CodexAuthError('Codex token refresh returned invalid JSON.', 'codex_refresh_invalid_json', true);
372
+ }
373
+ const refreshedAccessToken = requireTokenString(payload?.access_token, 'codex_refresh_missing_access_token', 'Codex token refresh response was missing access_token.');
374
+ const nextRefreshToken = typeof payload?.refresh_token === 'string' && payload.refresh_token.trim()
375
+ ? payload.refresh_token.trim()
376
+ : cleanRefreshToken;
377
+ return {
378
+ access_token: refreshedAccessToken,
379
+ refresh_token: nextRefreshToken,
380
+ last_refresh: nowIso(),
381
+ };
382
+ };
383
+ const resolveCodexBaseURL = (baseURL) => (baseURL ?? process.env.BROWSER_USE_CODEX_BASE_URL ?? '')
384
+ .trim()
385
+ .replace(/\/+$/, '') || DEFAULT_CODEX_BASE_URL;
386
+ export const resolveCodexRuntimeCredentials = async (options = {}) => {
387
+ const authStorePath = getCodexAuthStorePath(options);
388
+ const refreshIfExpiring = options.refreshIfExpiring ?? true;
389
+ const refreshSkewSeconds = options.refreshSkewSeconds ?? CODEX_ACCESS_TOKEN_REFRESH_SKEW_SECONDS;
390
+ const lockTimeoutMs = options.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;
391
+ let record = await readCodexTokens(options);
392
+ let tokens = { ...record.tokens };
393
+ let shouldRefresh = Boolean(options.forceRefresh);
394
+ if (!shouldRefresh && refreshIfExpiring) {
395
+ shouldRefresh = codexAccessTokenIsExpiring(tokens.access_token, refreshSkewSeconds);
396
+ }
397
+ if (shouldRefresh) {
398
+ await withAuthStoreLock(authStorePath, async () => {
399
+ record = await readCodexTokens({ authStorePath });
400
+ tokens = { ...record.tokens };
401
+ let stillShouldRefresh = Boolean(options.forceRefresh);
402
+ if (!stillShouldRefresh && refreshIfExpiring) {
403
+ stillShouldRefresh = codexAccessTokenIsExpiring(tokens.access_token, refreshSkewSeconds);
404
+ }
405
+ if (!stillShouldRefresh) {
406
+ return;
407
+ }
408
+ const refreshed = await refreshCodexOAuth(tokens.access_token, tokens.refresh_token, options);
409
+ tokens = {
410
+ ...tokens,
411
+ access_token: refreshed.access_token,
412
+ refresh_token: refreshed.refresh_token,
413
+ };
414
+ const store = await readStore(authStorePath);
415
+ const providers = store.providers ?? {};
416
+ providers[CODEX_PROVIDER] = {
417
+ ...(providers[CODEX_PROVIDER] ?? {}),
418
+ tokens,
419
+ last_refresh: refreshed.last_refresh,
420
+ auth_mode: 'chatgpt',
421
+ source: providers[CODEX_PROVIDER]?.source ?? 'browser-use-auth-store',
422
+ };
423
+ await writeStore(authStorePath, {
424
+ ...store,
425
+ version: AUTH_STORE_VERSION,
426
+ active_provider: CODEX_PROVIDER,
427
+ providers,
428
+ });
429
+ record = {
430
+ ...record,
431
+ tokens,
432
+ last_refresh: refreshed.last_refresh,
433
+ };
434
+ }, lockTimeoutMs);
435
+ }
436
+ return {
437
+ provider: CODEX_PROVIDER,
438
+ base_url: resolveCodexBaseURL(options.baseURL),
439
+ api_key: tokens.access_token,
440
+ source: record.source ?? 'browser-use-auth-store',
441
+ last_refresh: record.last_refresh,
442
+ auth_mode: 'chatgpt',
443
+ };
444
+ };
445
+ export const getCodexAuthStatus = async (options = {}) => {
446
+ const authStorePath = getCodexAuthStorePath(options);
447
+ try {
448
+ const record = await readCodexTokens(options);
449
+ return {
450
+ authenticated: true,
451
+ auth_store_path: authStorePath,
452
+ provider: CODEX_PROVIDER,
453
+ base_url: resolveCodexBaseURL(options.baseURL),
454
+ source: record.source,
455
+ last_refresh: record.last_refresh,
456
+ access_token_expiring: codexAccessTokenIsExpiring(record.tokens.access_token),
457
+ };
458
+ }
459
+ catch (error) {
460
+ if (error instanceof CodexAuthError) {
461
+ return {
462
+ authenticated: false,
463
+ auth_store_path: authStorePath,
464
+ provider: CODEX_PROVIDER,
465
+ base_url: resolveCodexBaseURL(options.baseURL),
466
+ source: null,
467
+ last_refresh: null,
468
+ access_token_expiring: null,
469
+ error: {
470
+ code: error.code,
471
+ message: error.message,
472
+ relogin_required: error.relogin_required,
473
+ },
474
+ };
475
+ }
476
+ throw error;
477
+ }
478
+ };
479
+ export const saveImportedCodexCliTokens = async (options = {}) => {
480
+ const tokens = await importCodexCliTokens({
481
+ codexHome: options.codexHome,
482
+ authPath: options.codexAuthPath,
483
+ });
484
+ if (!tokens) {
485
+ return false;
486
+ }
487
+ await saveCodexTokens(tokens, {
488
+ ...options,
489
+ source: 'codex-cli-import',
490
+ lockTimeoutMs: options.lockTimeoutMs,
491
+ });
492
+ return true;
493
+ };
494
+ export const loginCodexDeviceCode = async (options = {}) => {
495
+ const issuer = (options.issuer ?? CODEX_DEVICE_AUTH_BASE_URL).replace(/\/+$/, '');
496
+ const clientId = options.clientId ?? CODEX_OAUTH_CLIENT_ID;
497
+ const output = options.stdout ?? process.stdout;
498
+ const sleepImpl = options.sleep ?? sleep;
499
+ const now = options.now ?? Date.now;
500
+ const maxWaitMs = options.maxWaitMs ?? 15 * 60 * 1000;
501
+ const userCodeResponse = await fetchWithTimeout(`${issuer}/api/accounts/deviceauth/usercode`, {
502
+ method: 'POST',
503
+ headers: { 'Content-Type': 'application/json' },
504
+ body: JSON.stringify({ client_id: clientId }),
505
+ }, options);
506
+ if (!userCodeResponse.ok) {
507
+ throw new CodexAuthError(`Device code request returned status ${userCodeResponse.status}.`, 'device_code_request_error');
508
+ }
509
+ const deviceData = (await userCodeResponse.json());
510
+ const userCode = deviceData?.user_code;
511
+ const deviceAuthId = deviceData?.device_auth_id;
512
+ const parsedPollInterval = Number.parseInt(String(deviceData?.interval ?? '5'), 10);
513
+ const pollIntervalMs = Number.isFinite(parsedPollInterval) && parsedPollInterval > 0
514
+ ? Math.max(3_000, parsedPollInterval * 1000)
515
+ : 5_000;
516
+ if (typeof userCode !== 'string' ||
517
+ !userCode.trim() ||
518
+ typeof deviceAuthId !== 'string' ||
519
+ !deviceAuthId.trim()) {
520
+ throw new CodexAuthError('Device code response missing required fields.', 'device_code_incomplete');
521
+ }
522
+ output.write('To continue, follow these steps:\n\n');
523
+ output.write(` 1. Open this URL in your browser:\n ${issuer}/codex/device\n\n`);
524
+ output.write(` 2. Enter this code:\n ${userCode}\n\n`);
525
+ output.write('Waiting for sign-in... (press Ctrl+C to cancel)\n');
526
+ const start = now();
527
+ let authorizationCode = null;
528
+ let codeVerifier = null;
529
+ while (now() - start < maxWaitMs) {
530
+ await sleepImpl(pollIntervalMs);
531
+ const pollResponse = await fetchWithTimeout(`${issuer}/api/accounts/deviceauth/token`, {
532
+ method: 'POST',
533
+ headers: { 'Content-Type': 'application/json' },
534
+ body: JSON.stringify({
535
+ device_auth_id: deviceAuthId,
536
+ user_code: userCode,
537
+ }),
538
+ }, options);
539
+ if (pollResponse.ok) {
540
+ const payload = (await pollResponse.json());
541
+ authorizationCode =
542
+ typeof payload?.authorization_code === 'string'
543
+ ? payload.authorization_code
544
+ : null;
545
+ codeVerifier =
546
+ typeof payload?.code_verifier === 'string'
547
+ ? payload.code_verifier
548
+ : null;
549
+ break;
550
+ }
551
+ if (pollResponse.status === 403 || pollResponse.status === 404) {
552
+ continue;
553
+ }
554
+ throw new CodexAuthError(`Device auth polling returned status ${pollResponse.status}.`, 'device_code_poll_error');
555
+ }
556
+ if (!authorizationCode || !codeVerifier) {
557
+ throw new CodexAuthError('Login timed out before authorization completed.', 'device_code_timeout', true);
558
+ }
559
+ const tokenResponse = await fetchWithTimeout(options.issuer ? `${issuer}/oauth/token` : CODEX_OAUTH_TOKEN_URL, {
560
+ method: 'POST',
561
+ headers: {
562
+ 'Content-Type': 'application/x-www-form-urlencoded',
563
+ },
564
+ body: new URLSearchParams({
565
+ grant_type: 'authorization_code',
566
+ code: authorizationCode,
567
+ redirect_uri: `${issuer}/deviceauth/callback`,
568
+ client_id: clientId,
569
+ code_verifier: codeVerifier,
570
+ }),
571
+ }, options);
572
+ if (!tokenResponse.ok) {
573
+ throw new CodexAuthError(`Token exchange returned status ${tokenResponse.status}.`, 'token_exchange_error', tokenResponse.status === 401 || tokenResponse.status === 403);
574
+ }
575
+ const tokenPayload = (await tokenResponse.json());
576
+ const accessToken = requireTokenString(tokenPayload?.access_token, 'token_exchange_no_access_token', 'Token exchange did not return an access_token.');
577
+ const refreshToken = requireTokenString(tokenPayload?.refresh_token, 'token_exchange_no_refresh_token', 'Token exchange did not return a refresh_token.');
578
+ return {
579
+ tokens: {
580
+ ...tokenPayload,
581
+ access_token: accessToken,
582
+ refresh_token: refreshToken,
583
+ },
584
+ base_url: resolveCodexBaseURL(),
585
+ last_refresh: nowIso(),
586
+ auth_mode: 'chatgpt',
587
+ source: 'device-code',
588
+ };
589
+ };
590
+ export const loginAndSaveCodexDeviceCode = async (options = {}) => {
591
+ const credentials = await loginCodexDeviceCode(options);
592
+ await saveCodexTokens(credentials.tokens, {
593
+ ...options,
594
+ lastRefresh: credentials.last_refresh,
595
+ source: credentials.source,
596
+ lockTimeoutMs: options.lockTimeoutMs,
597
+ });
598
+ return credentials;
599
+ };
@@ -0,0 +1,70 @@
1
+ import type { BaseChatModel, ChatInvokeOptions } from '../base.js';
2
+ import type { Message } from '../messages.js';
3
+ import { ChatInvokeCompletion } from '../views.js';
4
+ export interface ChatCodexOptions {
5
+ model?: string;
6
+ apiKey?: string | null;
7
+ baseURL?: string | null;
8
+ timeout?: number | null;
9
+ maxRetries?: number;
10
+ defaultHeaders?: Record<string, string> | null;
11
+ defaultQuery?: Record<string, string | undefined> | null;
12
+ fetchImplementation?: typeof fetch;
13
+ fetchOptions?: RequestInit | null;
14
+ reasoningEffort?: 'low' | 'medium' | 'high';
15
+ maxCompletionTokens?: number | null;
16
+ topP?: number | null;
17
+ seed?: number | null;
18
+ serviceTier?: 'auto' | 'default' | 'flex' | 'priority' | 'scale' | null;
19
+ include?: string[] | null;
20
+ addSchemaToSystemPrompt?: boolean;
21
+ dontForceStructuredOutput?: boolean;
22
+ removeMinItemsFromSchema?: boolean;
23
+ removeDefaultsFromSchema?: boolean;
24
+ configDir?: string | null;
25
+ authStorePath?: string | null;
26
+ refreshSkewSeconds?: number;
27
+ }
28
+ export declare class ChatCodex implements BaseChatModel {
29
+ model: string;
30
+ provider: string;
31
+ private apiKey;
32
+ private baseURL;
33
+ private timeout;
34
+ private maxRetries;
35
+ private defaultHeaders;
36
+ private defaultQuery;
37
+ private fetchImplementation;
38
+ private fetchOptions;
39
+ private reasoningEffort;
40
+ private maxCompletionTokens;
41
+ private topP;
42
+ private seed;
43
+ private serviceTier;
44
+ private include;
45
+ private addSchemaToSystemPrompt;
46
+ private dontForceStructuredOutput;
47
+ private removeMinItemsFromSchema;
48
+ private removeDefaultsFromSchema;
49
+ private configDir;
50
+ private authStorePath;
51
+ private refreshSkewSeconds;
52
+ constructor(options?: ChatCodexOptions);
53
+ get name(): string;
54
+ get model_name(): string;
55
+ private resolveClientConfig;
56
+ private createClient;
57
+ private getResponsesUsage;
58
+ private getResponseOutputText;
59
+ private collectResponse;
60
+ private getInputText;
61
+ private getModelParamsForResponses;
62
+ private buildCodexBackendInput;
63
+ private getZodSchemaCandidate;
64
+ private buildRequest;
65
+ ainvoke(messages: Message[], output_format?: undefined, options?: ChatInvokeOptions): Promise<ChatInvokeCompletion<string>>;
66
+ ainvoke<T>(messages: Message[], output_format: {
67
+ parse: (input: string) => T;
68
+ } | undefined, options?: ChatInvokeOptions): Promise<ChatInvokeCompletion<T>>;
69
+ private invokeResponses;
70
+ }