@theokit/sdk 4.10.1 → 4.11.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Auth subsystem (agent-builder M42): a new `@theokit/sdk/auth` sub-entry ships a credential store + OAuth engine, promoted DOWN from agent-builder's hardened M37 code, generalized to `provider: string` + a caller-supplied `CredentialStoreConfig` (no hardcoded client IDs). Public surface (`import { … } from "@theokit/sdk/auth"`): `resolveCredential(name)` returns a fresh (transparently-refreshed) `ResolvedCredential`; the credential store (`writeCredential`/`readAuthFile`/`readStoredOAuth`/`authFilePath`/`credentialHome`/`CredentialError`), the OAuth engine (`exchangeCode`/`refreshOAuthTokens`/`ensureFreshCredential`/`persistOAuthTokens`), the device flows (`deviceLogin`/`openaiDeviceLogin`/`requestDeviceCode`/`pollDeviceToken`/`requestOpenAIUsercode`/`parseJwtClaims`/`extractAccountId`), and the contract types (`CredentialStoreConfig`, `ResolvedCredential`, `StoredOAuthCredential`, `OAuthProviderConfig`, `OAuthTokens`, `HttpDeps`, `DeviceOAuthConfig`, `OpenAIDeviceConfig`, …). It sits at a dedicated sub-entry (DTS via tsc) — the same isolation as `@theokit/sdk/messages` / `/subscription` / `/sanitize` — because rollup-plugin-dts cannot bundle the modules into the main barrel. The credential store does an atomic O_EXCL + rename + fsync write at mode 0600 with 0700/0600 mode gates; the OAuth engine implements RFC 8628 device-grant + the OpenAI two-step headless flow + token exchange/refresh with in-flight-refresh coalescing (keyed by store path, rejected promise evicted — single-use refresh tokens are never double-spent) and a no-token-in-error discipline. The router's lazy-sentinel path now covers `oauth_device_code` / `oauth_external` so an oauth provider builds a client whose M41 `transform.fetch(ctx)` owns the fresh bearer at stream time — a mid-turn expiry refreshes without rebuilding the agent, and plain (api-key/env) profiles resolve byte-for-byte unchanged. Device-grant + JWT extraction + OpenAI two-step adapted from OpenCode (MIT); see NOTICE.
8
+
3
9
  ## 4.10.1
4
10
 
5
11
  ### Patch Changes
@@ -0,0 +1,552 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+ var fs = require('fs');
5
+ var path = require('path');
6
+ var zod = require('zod');
7
+
8
+ // src/internal/auth/credential-store.ts
9
+ function credentialHome(config, env = {}) {
10
+ const override = config.homeEnvVar !== void 0 ? env[config.homeEnvVar]?.trim() : void 0;
11
+ return override !== void 0 && override.length > 0 ? override : path.join(config.home, config.dirName);
12
+ }
13
+ function authFilePath(config, env = {}) {
14
+ return path.join(credentialHome(config, env), config.fileName);
15
+ }
16
+ var CredentialError = class extends Error {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = "CredentialError";
20
+ }
21
+ };
22
+ var apiFileSchema = zod.z.object({
23
+ type: zod.z.literal("api").optional(),
24
+ provider: zod.z.string().min(1).optional(),
25
+ api_key: zod.z.string()
26
+ }).strict();
27
+ var oauthFileSchema = zod.z.object({
28
+ type: zod.z.literal("oauth"),
29
+ provider: zod.z.string().min(1),
30
+ access: zod.z.string().min(1),
31
+ refresh: zod.z.string().min(1),
32
+ expires: zod.z.number(),
33
+ account_id: zod.z.string().optional()
34
+ }).strict();
35
+ var fileSchema = zod.z.union([oauthFileSchema, apiFileSchema]);
36
+ function assertSecureModes(dirPath, path) {
37
+ const dirMode = fs.statSync(dirPath).mode & 511;
38
+ if ((dirMode & 18) !== 0) {
39
+ throw new CredentialError(
40
+ `${dirPath} is writable by other users (mode ${dirMode.toString(8)}), so the credential file inside it can be replaced. Fix it with: chmod 700 ${dirPath}`
41
+ );
42
+ }
43
+ const mode = fs.statSync(path).mode & 511;
44
+ if ((mode & 63) !== 0) {
45
+ throw new CredentialError(
46
+ `${path} is readable by other users (mode ${mode.toString(8)}). A credential file must not be. Fix it with: chmod 600 ${path}`
47
+ );
48
+ }
49
+ }
50
+ function describeUnionError(parsed, err, path) {
51
+ const looksOAuth = typeof parsed === "object" && parsed !== null && parsed.type === "oauth";
52
+ const specific = looksOAuth ? oauthFileSchema.safeParse(parsed) : apiFileSchema.safeParse(parsed);
53
+ let issue;
54
+ if (!specific.success) {
55
+ issue = specific.error.issues[0];
56
+ } else if (err instanceof zod.z.ZodError) {
57
+ issue = err.issues[0];
58
+ }
59
+ return new CredentialError(
60
+ `${path}: ${issue?.message ?? String(err)} [${issue?.path.join(".") || "root"}]`
61
+ );
62
+ }
63
+ function parseStoredFile(raw, path) {
64
+ let parsed;
65
+ try {
66
+ parsed = JSON.parse(raw);
67
+ } catch {
68
+ throw new CredentialError(
69
+ `${path} is not valid JSON. Expected: {"provider": "<name>", "api_key": "..."}`
70
+ );
71
+ }
72
+ try {
73
+ return fileSchema.parse(parsed);
74
+ } catch (err) {
75
+ throw describeUnionError(parsed, err, path);
76
+ }
77
+ }
78
+ function readAuthFile(config, env = {}) {
79
+ const path = authFilePath(config, env);
80
+ let raw;
81
+ try {
82
+ raw = fs.readFileSync(path, "utf8");
83
+ } catch (err) {
84
+ if (err.code === "ENOENT") return void 0;
85
+ throw new CredentialError(`cannot read ${path}: ${err.message}`);
86
+ }
87
+ assertSecureModes(credentialHome(config, env), path);
88
+ return parseStoredFile(raw, path);
89
+ }
90
+ function readStoredOAuth(config, env = {}) {
91
+ const stored = readAuthFile(config, env);
92
+ return stored !== void 0 && stored.type === "oauth" ? stored : void 0;
93
+ }
94
+ function isOAuthWrite(c) {
95
+ return "type" in c && c.type === "oauth";
96
+ }
97
+ function buildStorePayload(cred) {
98
+ if (isOAuthWrite(cred)) {
99
+ if (cred.access.length === 0 || cred.refresh.length === 0) {
100
+ throw new CredentialError(
101
+ "refusing to write an oauth credential with an empty access/refresh token"
102
+ );
103
+ }
104
+ return {
105
+ type: "oauth",
106
+ provider: cred.provider,
107
+ access: cred.access,
108
+ refresh: cred.refresh,
109
+ expires: cred.expires,
110
+ ...cred.account_id !== void 0 ? { account_id: cred.account_id } : {}
111
+ };
112
+ }
113
+ if (typeof cred.apiKey !== "string" || cred.apiKey.length === 0) {
114
+ throw new CredentialError("refusing to write an empty API key");
115
+ }
116
+ return { provider: cred.provider, api_key: cred.apiKey };
117
+ }
118
+ function writeCredential(cred, config, env = {}) {
119
+ const payload = buildStorePayload(cred);
120
+ const dir = credentialHome(config, env);
121
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
122
+ fs.chmodSync(dir, 448);
123
+ const path = authFilePath(config, env);
124
+ const tmp = `${path}.tmp-${crypto.randomBytes(8).toString("hex")}`;
125
+ try {
126
+ const fd = fs.openSync(tmp, "wx", 384);
127
+ try {
128
+ fs.writeFileSync(fd, `${JSON.stringify(payload, null, 2)}
129
+ `);
130
+ fs.fsyncSync(fd);
131
+ } finally {
132
+ fs.closeSync(fd);
133
+ }
134
+ fs.chmodSync(tmp, 384);
135
+ fs.renameSync(tmp, path);
136
+ } catch (err) {
137
+ try {
138
+ fs.unlinkSync(tmp);
139
+ } catch {
140
+ }
141
+ throw new CredentialError(`cannot write ${path}: ${err.message}`);
142
+ }
143
+ return path;
144
+ }
145
+
146
+ // src/server/auth/errors.ts
147
+ var AuthCallbackError = class extends Error {
148
+ name = "AuthCallbackError";
149
+ code;
150
+ constructor(code, message) {
151
+ super(message ?? `OAuth callback error: ${code}`);
152
+ this.code = code;
153
+ }
154
+ };
155
+
156
+ // src/internal/auth/oauth-engine.ts
157
+ var REFRESH_SKEW_MS = 6e4;
158
+ function parseTokenResponse(body, now) {
159
+ const b = body;
160
+ if (typeof b.access_token !== "string" || b.access_token.length === 0) {
161
+ throw new AuthCallbackError(
162
+ "oauth_token_exchange_failed",
163
+ "token response had no access_token"
164
+ );
165
+ }
166
+ if (typeof b.refresh_token !== "string" || b.refresh_token.length === 0) {
167
+ throw new AuthCallbackError(
168
+ "oauth_token_exchange_failed",
169
+ "token response had no refresh_token"
170
+ );
171
+ }
172
+ const expiresIn = typeof b.expires_in === "number" ? b.expires_in : 3600;
173
+ return {
174
+ access: b.access_token,
175
+ refresh: b.refresh_token,
176
+ expires: now + expiresIn * 1e3,
177
+ ...typeof b.account_id === "string" ? { accountId: b.account_id } : {}
178
+ };
179
+ }
180
+ async function postGrant(config, form, deps) {
181
+ let res;
182
+ try {
183
+ res = await deps.fetch(config.tokenEndpoint, {
184
+ method: "POST",
185
+ headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
186
+ body: new URLSearchParams(form).toString()
187
+ });
188
+ } catch (err) {
189
+ throw new AuthCallbackError(
190
+ "oauth_token_exchange_failed",
191
+ `token endpoint request failed: ${err.message}`
192
+ );
193
+ }
194
+ if (!res.ok) {
195
+ throw new AuthCallbackError(
196
+ "oauth_token_exchange_failed",
197
+ `token endpoint returned HTTP ${res.status}`
198
+ );
199
+ }
200
+ let json;
201
+ try {
202
+ json = await res.json();
203
+ } catch {
204
+ throw new AuthCallbackError("oauth_token_exchange_failed", "token response was not valid JSON");
205
+ }
206
+ return parseTokenResponse(json, deps.now());
207
+ }
208
+ function exchangeCode(config, input, deps) {
209
+ return postGrant(
210
+ config,
211
+ {
212
+ grant_type: "authorization_code",
213
+ code: input.code,
214
+ redirect_uri: config.redirectUri,
215
+ client_id: config.clientId,
216
+ code_verifier: input.verifier
217
+ },
218
+ deps
219
+ );
220
+ }
221
+ function refreshOAuthTokens(config, refresh, deps) {
222
+ return postGrant(
223
+ config,
224
+ { grant_type: "refresh_token", refresh_token: refresh, client_id: config.clientId },
225
+ deps
226
+ );
227
+ }
228
+ function persistOAuthTokens(provider, tokens, store, env = {}) {
229
+ return writeCredential(
230
+ {
231
+ type: "oauth",
232
+ provider,
233
+ access: tokens.access,
234
+ refresh: tokens.refresh,
235
+ expires: tokens.expires,
236
+ ...tokens.accountId !== void 0 ? { account_id: tokens.accountId } : {}
237
+ },
238
+ store,
239
+ env
240
+ );
241
+ }
242
+ var inFlightRefresh = /* @__PURE__ */ new Map();
243
+ async function ensureFreshCredential(resolved, opts, deps) {
244
+ if (resolved.kind !== "oauth") return resolved;
245
+ const now = deps.now();
246
+ if (resolved.expiresAt !== void 0 && resolved.expiresAt > now + REFRESH_SKEW_MS) {
247
+ return resolved;
248
+ }
249
+ const env = opts.env ?? {};
250
+ const path = authFilePath(opts.store, env);
251
+ let refresh = inFlightRefresh.get(path);
252
+ if (refresh === void 0) {
253
+ refresh = (async () => {
254
+ const stored = readStoredOAuth(opts.store, env);
255
+ if (stored === void 0) {
256
+ throw new AuthCallbackError(
257
+ "oauth_token_exchange_failed",
258
+ "no stored oauth credential to refresh"
259
+ );
260
+ }
261
+ const fresh2 = await refreshOAuthTokens(opts.config, stored.refresh, deps);
262
+ persistOAuthTokens(resolved.provider, fresh2, opts.store, env);
263
+ return fresh2;
264
+ })();
265
+ inFlightRefresh.set(path, refresh);
266
+ refresh.finally(() => inFlightRefresh.delete(path)).catch(() => {
267
+ });
268
+ }
269
+ const fresh = await refresh;
270
+ return {
271
+ kind: "oauth",
272
+ provider: resolved.provider,
273
+ apiKey: fresh.access,
274
+ source: resolved.source,
275
+ inferred: false,
276
+ expiresAt: fresh.expires
277
+ };
278
+ }
279
+
280
+ // src/internal/auth/oauth-device.ts
281
+ var POLLING_SAFETY_MARGIN_MS = 3e3;
282
+ async function requestDeviceCode(config, deps) {
283
+ let res;
284
+ try {
285
+ res = await deps.fetch(config.deviceCodeEndpoint, {
286
+ method: "POST",
287
+ headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
288
+ body: new URLSearchParams({
289
+ client_id: config.clientId,
290
+ scope: config.scopes.join(" ")
291
+ }).toString()
292
+ });
293
+ } catch (err) {
294
+ throw new AuthCallbackError(
295
+ "oauth_device_authorization_failed",
296
+ `device request failed: ${err.message}`
297
+ );
298
+ }
299
+ if (!res.ok) {
300
+ throw new AuthCallbackError(
301
+ "oauth_device_authorization_failed",
302
+ `device endpoint returned HTTP ${res.status}`
303
+ );
304
+ }
305
+ const data = await res.json();
306
+ if (typeof data.device_code !== "string" || typeof data.user_code !== "string") {
307
+ throw new AuthCallbackError(
308
+ "oauth_device_authorization_failed",
309
+ "device response missing device_code/user_code"
310
+ );
311
+ }
312
+ const verification = typeof data.verification_uri === "string" ? data.verification_uri : typeof data.verification_uri_complete === "string" ? data.verification_uri_complete : "";
313
+ return {
314
+ deviceCode: data.device_code,
315
+ userCode: data.user_code,
316
+ verificationUri: verification,
317
+ interval: typeof data.interval === "number" && data.interval > 0 ? data.interval : 5,
318
+ expiresIn: typeof data.expires_in === "number" && data.expires_in > 0 ? data.expires_in : 900
319
+ };
320
+ }
321
+ async function pollDeviceToken(config, grant, deps) {
322
+ const deadline = deps.now() + grant.expiresIn * 1e3;
323
+ let intervalMs = grant.interval * 1e3;
324
+ while (deps.now() < deadline) {
325
+ let res;
326
+ try {
327
+ res = await deps.fetch(config.tokenEndpoint, {
328
+ method: "POST",
329
+ headers: {
330
+ "content-type": "application/x-www-form-urlencoded",
331
+ accept: "application/json"
332
+ },
333
+ body: new URLSearchParams({
334
+ client_id: config.clientId,
335
+ device_code: grant.deviceCode,
336
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
337
+ }).toString()
338
+ });
339
+ } catch (err) {
340
+ throw new AuthCallbackError(
341
+ "oauth_token_exchange_failed",
342
+ `device token poll failed: ${err.message}`
343
+ );
344
+ }
345
+ const data = await res.json().catch(() => ({}));
346
+ if (!res.ok && typeof data.error !== "string") {
347
+ throw new AuthCallbackError(
348
+ "oauth_token_exchange_failed",
349
+ `device token endpoint returned HTTP ${res.status}`
350
+ );
351
+ }
352
+ if (typeof data.access_token === "string" && data.access_token.length > 0) {
353
+ const expiresIn = typeof data.expires_in === "number" ? data.expires_in : 0;
354
+ const accountId = extractAccountId({
355
+ access_token: data.access_token,
356
+ id_token: data.id_token
357
+ });
358
+ return {
359
+ access: data.access_token,
360
+ // Some device-grant providers (e.g. GitHub) issue no separate refresh token — reuse the access
361
+ // token as the refresh handle, mirroring OpenCode's copilot flow.
362
+ refresh: data.refresh_token ?? data.access_token,
363
+ expires: deps.now() + expiresIn * 1e3,
364
+ ...accountId !== void 0 ? { accountId } : {}
365
+ };
366
+ }
367
+ if (data.error === "authorization_pending") {
368
+ await deps.sleep(intervalMs + POLLING_SAFETY_MARGIN_MS);
369
+ continue;
370
+ }
371
+ if (data.error === "slow_down") {
372
+ intervalMs = (typeof data.interval === "number" && data.interval > 0 ? data.interval : grant.interval + 5) * 1e3;
373
+ await deps.sleep(intervalMs + POLLING_SAFETY_MARGIN_MS);
374
+ continue;
375
+ }
376
+ if (typeof data.error === "string") {
377
+ throw new AuthCallbackError(
378
+ "oauth_token_exchange_failed",
379
+ `device authorization rejected: ${data.error}`
380
+ );
381
+ }
382
+ await deps.sleep(intervalMs + POLLING_SAFETY_MARGIN_MS);
383
+ }
384
+ throw new AuthCallbackError(
385
+ "oauth_device_code_expired",
386
+ "the device code expired before it was approved"
387
+ );
388
+ }
389
+ async function deviceLogin(config, deps, hooks) {
390
+ const grant = await requestDeviceCode(config, deps);
391
+ hooks.onPrompt({
392
+ userCode: grant.userCode,
393
+ verificationUri: grant.verificationUri,
394
+ expiresIn: grant.expiresIn
395
+ });
396
+ return pollDeviceToken(config, grant, deps);
397
+ }
398
+ async function requestOpenAIUsercode(config, deps) {
399
+ let res;
400
+ try {
401
+ res = await deps.fetch(config.deviceUsercodeEndpoint, {
402
+ method: "POST",
403
+ headers: { "content-type": "application/json", accept: "application/json" },
404
+ body: JSON.stringify({ client_id: config.clientId })
405
+ });
406
+ } catch (err) {
407
+ throw new AuthCallbackError(
408
+ "oauth_device_authorization_failed",
409
+ `usercode request failed: ${err.message}`
410
+ );
411
+ }
412
+ if (!res.ok) {
413
+ throw new AuthCallbackError(
414
+ "oauth_device_authorization_failed",
415
+ `usercode endpoint returned HTTP ${res.status}`
416
+ );
417
+ }
418
+ const data = await res.json();
419
+ if (typeof data.device_auth_id !== "string" || typeof data.user_code !== "string") {
420
+ throw new AuthCallbackError(
421
+ "oauth_device_authorization_failed",
422
+ "usercode response missing device_auth_id/user_code"
423
+ );
424
+ }
425
+ const interval = Math.max(
426
+ typeof data.interval === "string" ? parseInt(data.interval, 10) || 5 : 5,
427
+ 1
428
+ );
429
+ return { deviceAuthId: data.device_auth_id, userCode: data.user_code, interval };
430
+ }
431
+ async function openaiDeviceLogin(config, deps, hooks) {
432
+ const { deviceAuthId, userCode, interval } = await requestOpenAIUsercode(config, deps);
433
+ hooks.onPrompt({ userCode, verificationUri: config.verificationUri });
434
+ const intervalMs = interval * 1e3;
435
+ const deadline = deps.now() + 15 * 60 * 1e3;
436
+ while (deps.now() < deadline) {
437
+ let res;
438
+ try {
439
+ res = await deps.fetch(config.devicePollEndpoint, {
440
+ method: "POST",
441
+ headers: { "content-type": "application/json", accept: "application/json" },
442
+ body: JSON.stringify({ device_auth_id: deviceAuthId, user_code: userCode })
443
+ });
444
+ } catch (err) {
445
+ throw new AuthCallbackError(
446
+ "oauth_token_exchange_failed",
447
+ `device poll failed: ${err.message}`
448
+ );
449
+ }
450
+ if (res.ok) {
451
+ const data = await res.json();
452
+ if (typeof data.authorization_code !== "string" || typeof data.code_verifier !== "string") {
453
+ throw new AuthCallbackError(
454
+ "oauth_token_exchange_failed",
455
+ "device poll returned no authorization_code"
456
+ );
457
+ }
458
+ return exchangeCode(
459
+ config,
460
+ { code: data.authorization_code, verifier: data.code_verifier },
461
+ deps
462
+ );
463
+ }
464
+ if (res.status !== 403 && res.status !== 404) {
465
+ throw new AuthCallbackError(
466
+ "oauth_token_exchange_failed",
467
+ `device poll returned HTTP ${res.status}`
468
+ );
469
+ }
470
+ await deps.sleep(intervalMs + POLLING_SAFETY_MARGIN_MS);
471
+ }
472
+ throw new AuthCallbackError(
473
+ "oauth_device_code_expired",
474
+ "the device authorization expired before approval"
475
+ );
476
+ }
477
+ function parseJwtClaims(token) {
478
+ const parts = token.split(".");
479
+ if (parts.length !== 3) return void 0;
480
+ try {
481
+ return JSON.parse(Buffer.from(parts[1], "base64url").toString());
482
+ } catch {
483
+ return void 0;
484
+ }
485
+ }
486
+ function extractAccountId(tokens) {
487
+ for (const token of [tokens.id_token, tokens.access_token]) {
488
+ if (token === void 0) continue;
489
+ const claims = parseJwtClaims(token);
490
+ const id = claims?.chatgpt_account_id ?? claims?.["https://api.openai.com/auth"]?.chatgpt_account_id ?? claims?.organizations?.[0]?.id;
491
+ if (id !== void 0) return id;
492
+ }
493
+ return void 0;
494
+ }
495
+
496
+ // src/internal/auth/resolve-credential.ts
497
+ async function resolveOAuth(stored, path, opts, env) {
498
+ if (stored.provider !== opts.provider) return void 0;
499
+ const base = {
500
+ kind: "oauth",
501
+ provider: opts.provider,
502
+ apiKey: stored.access,
503
+ source: path,
504
+ inferred: false,
505
+ expiresAt: stored.expires
506
+ };
507
+ if (opts.oauth === void 0) return base;
508
+ const deps = {
509
+ fetch: opts.deps?.fetch ?? fetch,
510
+ now: opts.deps?.now ?? (() => Date.now())
511
+ };
512
+ return ensureFreshCredential(base, { config: opts.oauth, store: opts.store, env }, deps);
513
+ }
514
+ async function resolveCredential(opts) {
515
+ const env = opts.env ?? {};
516
+ const stored = readAuthFile(opts.store, env);
517
+ if (stored === void 0) return void 0;
518
+ const path = authFilePath(opts.store, env);
519
+ if (stored.type === "oauth") {
520
+ return resolveOAuth(stored, path, opts, env);
521
+ }
522
+ if (stored.api_key.length === 0) return void 0;
523
+ if (stored.provider !== void 0 && stored.provider !== opts.provider) return void 0;
524
+ return {
525
+ kind: "api",
526
+ provider: opts.provider,
527
+ apiKey: stored.api_key,
528
+ source: path,
529
+ inferred: stored.provider === void 0
530
+ };
531
+ }
532
+
533
+ exports.CredentialError = CredentialError;
534
+ exports.authFilePath = authFilePath;
535
+ exports.credentialHome = credentialHome;
536
+ exports.deviceLogin = deviceLogin;
537
+ exports.ensureFreshCredential = ensureFreshCredential;
538
+ exports.exchangeCode = exchangeCode;
539
+ exports.extractAccountId = extractAccountId;
540
+ exports.openaiDeviceLogin = openaiDeviceLogin;
541
+ exports.parseJwtClaims = parseJwtClaims;
542
+ exports.persistOAuthTokens = persistOAuthTokens;
543
+ exports.pollDeviceToken = pollDeviceToken;
544
+ exports.readAuthFile = readAuthFile;
545
+ exports.readStoredOAuth = readStoredOAuth;
546
+ exports.refreshOAuthTokens = refreshOAuthTokens;
547
+ exports.requestDeviceCode = requestDeviceCode;
548
+ exports.requestOpenAIUsercode = requestOpenAIUsercode;
549
+ exports.resolveCredential = resolveCredential;
550
+ exports.writeCredential = writeCredential;
551
+ //# sourceMappingURL=index.cjs.map
552
+ //# sourceMappingURL=index.cjs.map