dsh-coding-subscription-oauth 0.6.5 → 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 (55) hide show
  1. package/CHANGELOG.md +19 -2
  2. package/CONTRIBUTING.md +1 -1
  3. package/INSTALL.md +13 -8
  4. package/README.de.md +5 -5
  5. package/README.es.md +5 -5
  6. package/README.fr.md +5 -5
  7. package/README.ja.md +5 -5
  8. package/README.ko.md +5 -5
  9. package/README.md +7 -6
  10. package/README.pt-BR.md +5 -5
  11. package/README.ru.md +5 -5
  12. package/README.zh-CN.md +7 -6
  13. package/compatibility/dsh-bom.json +8 -2
  14. package/docs/00-project-rules.md +3 -2
  15. package/docs/02-architecture.md +5 -5
  16. package/docs/02-architecture.zh-CN.md +5 -5
  17. package/lib/adapter.d.ts.map +1 -1
  18. package/lib/auth-routes.d.ts +10 -1
  19. package/lib/auth-routes.d.ts.map +1 -1
  20. package/lib/bin.js +483 -50
  21. package/lib/bin.js.map +4 -4
  22. package/lib/client.js +5 -5
  23. package/lib/client.js.map +4 -4
  24. package/lib/compatibility.d.ts +0 -1
  25. package/lib/compatibility.d.ts.map +1 -1
  26. package/lib/gateway-protocol.d.ts +3 -3
  27. package/lib/gateway-protocol.d.ts.map +1 -1
  28. package/lib/grok-errors.d.ts +2 -2
  29. package/lib/grok-errors.d.ts.map +1 -1
  30. package/lib/http-json.d.ts +2 -2
  31. package/lib/http-json.d.ts.map +1 -1
  32. package/lib/ids.d.ts +4 -0
  33. package/lib/ids.d.ts.map +1 -1
  34. package/lib/index.d.ts +2 -1
  35. package/lib/index.d.ts.map +1 -1
  36. package/lib/index.js +679 -163
  37. package/lib/index.js.map +4 -4
  38. package/lib/kimi-errors.d.ts +2 -2
  39. package/lib/kimi-errors.d.ts.map +1 -1
  40. package/lib/oauth-session.d.ts +2 -0
  41. package/lib/oauth-session.d.ts.map +1 -1
  42. package/lib/session.d.ts +2 -0
  43. package/lib/session.d.ts.map +1 -1
  44. package/lib/store.d.ts +73 -1
  45. package/lib/store.d.ts.map +1 -1
  46. package/package.json +9 -8
  47. package/scripts/verify-adapter-host.mjs +70 -0
  48. package/lib/runtime/gateway-protocol.d.ts +0 -47
  49. package/lib/runtime/gateway-protocol.d.ts.map +0 -1
  50. package/lib/runtime/grok-errors.d.ts +0 -13
  51. package/lib/runtime/grok-errors.d.ts.map +0 -1
  52. package/lib/runtime/http-json.d.ts +0 -10
  53. package/lib/runtime/http-json.d.ts.map +0 -1
  54. package/lib/runtime/kimi-errors.d.ts +0 -13
  55. package/lib/runtime/kimi-errors.d.ts.map +0 -1
package/lib/bin.js CHANGED
@@ -25620,7 +25620,7 @@ import { readFile, stat } from "node:fs/promises";
25620
25620
  import { homedir } from "node:os";
25621
25621
  import { join, resolve } from "node:path";
25622
25622
 
25623
- // node_modules/.pnpm/dsh-coding-oauth-core@0.1.1/node_modules/dsh-coding-oauth-core/lib/proxy.js
25623
+ // node_modules/.pnpm/dsh-coding-oauth-core@0.1.2_@deepseek-ai+dsh-llm@0.1.1-rc.2_@deepseek-ai+cordis@4.0.1_@_82bbdc1424db474a5d3f648945c0fadb/node_modules/dsh-coding-oauth-core/lib/proxy.js
25624
25624
  var import_undici = __toESM(require_undici(), 1);
25625
25625
  var DEFAULT_PROXIED_HOSTS = [
25626
25626
  "auth.x.ai",
@@ -25749,7 +25749,7 @@ function codingOAuthProxyUnreachableHint() {
25749
25749
  return codingOAuthProxyInEffect() === void 0 ? "" : "; check that CODING_OAUTH_PROXY is reachable";
25750
25750
  }
25751
25751
 
25752
- // node_modules/.pnpm/dsh-coding-oauth-core@0.1.1/node_modules/dsh-coding-oauth-core/lib/ids.js
25752
+ // node_modules/.pnpm/dsh-coding-oauth-core@0.1.2_@deepseek-ai+dsh-llm@0.1.1-rc.2_@deepseek-ai+cordis@4.0.1_@_82bbdc1424db474a5d3f648945c0fadb/node_modules/dsh-coding-oauth-core/lib/ids.js
25753
25753
  var XAI_PI_PROVIDER = "xai";
25754
25754
  var CODEX_PI_PROVIDER = "openai-codex";
25755
25755
  var KIMI_PI_PROVIDER = "kimi-coding";
@@ -25768,7 +25768,7 @@ var KIMI_CODE_OAUTH_MODELS_CACHE_FILENAME = ".kimi-code-oauth-models.json";
25768
25768
  var CLAUDE_CODE_OAUTH_MODELS_CACHE_FILENAME = ".claude-code-oauth-models.json";
25769
25769
  var DEFAULT_GROK_BUILD_MODEL = "grok-4.6";
25770
25770
 
25771
- // node_modules/.pnpm/dsh-coding-oauth-core@0.1.1/node_modules/dsh-coding-oauth-core/lib/state-contract.js
25771
+ // node_modules/.pnpm/dsh-coding-oauth-core@0.1.2_@deepseek-ai+dsh-llm@0.1.1-rc.2_@deepseek-ai+cordis@4.0.1_@_82bbdc1424db474a5d3f648945c0fadb/node_modules/dsh-coding-oauth-core/lib/state-contract.js
25772
25772
  var GROK_BUILD_AUTH_STATUS_PATH = "/plugins/dsh-grok-build/auth/status";
25773
25773
  var GROK_BUILD_AUTH_LOGIN_PATH = "/plugins/dsh-grok-build/auth/login";
25774
25774
  var GROK_BUILD_AUTH_LOGIN_CODE_PATH = "/plugins/dsh-grok-build/auth/login/code";
@@ -25818,6 +25818,17 @@ var CODING_OAUTH_MANAGEMENT_PATHS = Object.freeze([
25818
25818
  GATEWAY_ROTATE_PATH
25819
25819
  ]);
25820
25820
 
25821
+ // node_modules/.pnpm/dsh-coding-oauth-core@0.1.2_@deepseek-ai+dsh-llm@0.1.1-rc.2_@deepseek-ai+cordis@4.0.1_@_82bbdc1424db474a5d3f648945c0fadb/node_modules/dsh-coding-oauth-core/lib/http-json.js
25822
+ var JSON_BODY_LIMIT_BYTES = 64 * 1024;
25823
+
25824
+ // node_modules/.pnpm/dsh-coding-oauth-core@0.1.2_@deepseek-ai+dsh-llm@0.1.1-rc.2_@deepseek-ai+cordis@4.0.1_@_82bbdc1424db474a5d3f648945c0fadb/node_modules/dsh-coding-oauth-core/lib/kimi-errors.js
25825
+ import { CONTEXT_WINDOW_EXCEEDED_CODE, isContextWindowExceededError } from "@deepseek-ai/dsh-llm";
25826
+
25827
+ // src/ids.ts
25828
+ var CODING_OAUTH_API_BASE = "/plugins/dsh-grok-build";
25829
+ var CODING_OAUTH_ACCOUNTS_SET_ACTIVE_PATH = `${CODING_OAUTH_API_BASE}/oauth/accounts/set-active`;
25830
+ var CODING_OAUTH_ACCOUNTS_REMOVE_PATH = `${CODING_OAUTH_API_BASE}/oauth/accounts/remove`;
25831
+
25821
25832
  // src/grok-import.ts
25822
25833
  var DEFAULT_TOKEN_LIFETIME_MS = 60 * 60 * 1e3;
25823
25834
  function isENOENT(error) {
@@ -25919,6 +25930,7 @@ async function importGrokAuth(store, filename = grokAuthPath()) {
25919
25930
  }
25920
25931
 
25921
25932
  // src/store.ts
25933
+ import { randomBytes } from "node:crypto";
25922
25934
  import { mkdir, rm } from "node:fs/promises";
25923
25935
  import { dirname, join as join3, resolve as resolve3 } from "node:path";
25924
25936
  import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
@@ -26078,25 +26090,17 @@ function currentUid() {
26078
26090
  }
26079
26091
 
26080
26092
  // src/store.ts
26081
- var AUTH_FORMAT_VERSION = 1;
26082
- function parseDocument(text, filename, label) {
26083
- let value;
26084
- try {
26085
- value = JSON.parse(text);
26086
- } catch {
26087
- throw new Error(`${label}: ${filename} is not valid JSON`);
26088
- }
26089
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
26090
- throw new Error(`${label}: ${filename} must contain an object`);
26091
- }
26092
- const document = value;
26093
- if (document["version"] !== AUTH_FORMAT_VERSION) {
26094
- throw new Error(`${label}: ${filename} has unsupported auth format version ${String(document["version"])}`);
26095
- }
26096
- if (Object.keys(document).some((key) => key !== "version" && key !== "credential")) {
26097
- throw new Error(`${label}: ${filename} contains an unknown top-level field`);
26098
- }
26099
- const raw = document["credential"];
26093
+ var AUTH_FORMAT_VERSION = 2;
26094
+ var AUTH_FORMAT_VERSION_V1 = 1;
26095
+ var OAUTH_MAX_ACCOUNTS = 8;
26096
+ var MAX_ACCOUNTS = OAUTH_MAX_ACCOUNTS;
26097
+ var LEGACY_ACCOUNT_ID = "legacy";
26098
+ var ACCOUNT_ID_PATTERN = /^[A-Za-z0-9._:@+-]+$/u;
26099
+ var UNCHANGED = /* @__PURE__ */ Symbol("unchanged");
26100
+ function isValidAccountId(value) {
26101
+ return typeof value === "string" && value.length > 0 && value.length <= 128 && ACCOUNT_ID_PATTERN.test(value);
26102
+ }
26103
+ function parseOAuthCredential(raw, filename, label) {
26100
26104
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
26101
26105
  throw new Error(`${label}: ${filename} credential must be an object`);
26102
26106
  }
@@ -26105,23 +26109,178 @@ function parseDocument(text, filename, label) {
26105
26109
  if (Object.keys(credential).some((key) => !allowed.has(key))) {
26106
26110
  throw new Error(`${label}: ${filename} credential contains an unknown field`);
26107
26111
  }
26108
- if (credential["type"] !== "oauth") throw new Error(`${label}: ${filename} credential type must be oauth`);
26112
+ if (credential.type !== "oauth") throw new Error(`${label}: ${filename} credential type must be oauth`);
26109
26113
  for (const key of ["access", "refresh"]) {
26110
26114
  if (typeof credential[key] !== "string" || credential[key].length === 0) {
26111
26115
  throw new Error(`${label}: ${filename} credential ${key} must be a non-empty string`);
26112
26116
  }
26113
26117
  }
26114
- if (credential["accountId"] !== void 0 && (typeof credential["accountId"] !== "string" || credential["accountId"].length === 0)) {
26118
+ if (credential.accountId !== void 0 && (typeof credential.accountId !== "string" || credential.accountId.length === 0)) {
26115
26119
  throw new Error(`${label}: ${filename} credential accountId must be a non-empty string when present`);
26116
26120
  }
26117
- if (typeof credential["expires"] !== "number" || !Number.isFinite(credential["expires"]) || credential["expires"] <= 0) {
26121
+ if (typeof credential.expires !== "number" || !Number.isFinite(credential.expires) || credential.expires <= 0) {
26118
26122
  throw new Error(`${label}: ${filename} credential expires must be a positive finite number`);
26119
26123
  }
26120
- return { version: AUTH_FORMAT_VERSION, credential };
26124
+ return credential;
26125
+ }
26126
+ function parseAccountRecord(raw, filename, label, index) {
26127
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
26128
+ throw new Error(`${label}: ${filename} accounts[${String(index)}] must be an object`);
26129
+ }
26130
+ const record = raw;
26131
+ const allowed = /* @__PURE__ */ new Set(["id", "label", "credential", "createdAt"]);
26132
+ if (Object.keys(record).some((key) => !allowed.has(key))) {
26133
+ throw new Error(`${label}: ${filename} accounts[${String(index)}] contains an unknown field`);
26134
+ }
26135
+ if (!isValidAccountId(record.id)) {
26136
+ throw new Error(`${label}: ${filename} accounts[${String(index)}].id is not a valid account id`);
26137
+ }
26138
+ if (record.label !== void 0 && (typeof record.label !== "string" || record.label.length === 0)) {
26139
+ throw new Error(`${label}: ${filename} accounts[${String(index)}].label must be a non-empty string when present`);
26140
+ }
26141
+ if (typeof record.createdAt !== "number" || !Number.isFinite(record.createdAt) || record.createdAt <= 0) {
26142
+ throw new Error(`${label}: ${filename} accounts[${String(index)}].createdAt must be a positive finite number`);
26143
+ }
26144
+ const credential = parseOAuthCredential(record.credential, filename, label);
26145
+ return {
26146
+ id: record.id,
26147
+ ...record.label === void 0 ? {} : { label: record.label },
26148
+ credential,
26149
+ createdAt: record.createdAt
26150
+ };
26151
+ }
26152
+ function parseDocumentV2(document, filename, label) {
26153
+ if (Object.keys(document).some((key) => key !== "version" && key !== "activeAccountId" && key !== "accounts")) {
26154
+ throw new Error(`${label}: ${filename} contains an unknown top-level field`);
26155
+ }
26156
+ if (!isValidAccountId(document.activeAccountId)) {
26157
+ throw new Error(`${label}: ${filename} activeAccountId is not a valid account id`);
26158
+ }
26159
+ if (!Array.isArray(document.accounts)) {
26160
+ throw new Error(`${label}: ${filename} accounts must be an array`);
26161
+ }
26162
+ if (document.accounts.length < 1 || document.accounts.length > MAX_ACCOUNTS) {
26163
+ throw new Error(`${label}: ${filename} accounts must contain between 1 and ${String(MAX_ACCOUNTS)} entries`);
26164
+ }
26165
+ const accounts = document.accounts.map((entry, index) => parseAccountRecord(entry, filename, label, index));
26166
+ const ids = /* @__PURE__ */ new Set();
26167
+ for (const account of accounts) {
26168
+ if (ids.has(account.id)) {
26169
+ throw new Error(`${label}: ${filename} accounts contains a duplicate id`);
26170
+ }
26171
+ ids.add(account.id);
26172
+ }
26173
+ if (!ids.has(document.activeAccountId)) {
26174
+ throw new Error(`${label}: ${filename} activeAccountId does not match any account`);
26175
+ }
26176
+ return {
26177
+ version: AUTH_FORMAT_VERSION,
26178
+ activeAccountId: document.activeAccountId,
26179
+ accounts
26180
+ };
26181
+ }
26182
+ function parseDocumentV1(document, filename, label) {
26183
+ if (Object.keys(document).some((key) => key !== "version" && key !== "credential")) {
26184
+ throw new Error(`${label}: ${filename} contains an unknown top-level field`);
26185
+ }
26186
+ return parseOAuthCredential(document.credential, filename, label);
26187
+ }
26188
+ function parseDocument(text, filename, label) {
26189
+ let value;
26190
+ try {
26191
+ value = JSON.parse(text);
26192
+ } catch {
26193
+ throw new Error(`${label}: ${filename} is not valid JSON`);
26194
+ }
26195
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
26196
+ throw new Error(`${label}: ${filename} must contain an object`);
26197
+ }
26198
+ const document = value;
26199
+ if (document.version === AUTH_FORMAT_VERSION_V1) {
26200
+ return { document: migrateV1Credential(parseDocumentV1(document, filename, label)), migratedFromV1: true };
26201
+ }
26202
+ if (document.version === AUTH_FORMAT_VERSION) {
26203
+ return { document: parseDocumentV2(document, filename, label), migratedFromV1: false };
26204
+ }
26205
+ throw new Error(`${label}: ${filename} has unsupported auth format version ${String(document.version)}`);
26206
+ }
26207
+ function migrateV1Credential(credential) {
26208
+ const accountIdField = credential.accountId;
26209
+ const id = isValidAccountId(accountIdField) ? accountIdField : LEGACY_ACCOUNT_ID;
26210
+ return {
26211
+ version: AUTH_FORMAT_VERSION,
26212
+ activeAccountId: id,
26213
+ accounts: [
26214
+ {
26215
+ id,
26216
+ credential: cloneCredential(credential),
26217
+ createdAt: Date.now()
26218
+ }
26219
+ ]
26220
+ };
26121
26221
  }
26122
26222
  function cloneCredential(credential) {
26123
26223
  return structuredClone(credential);
26124
26224
  }
26225
+ function cloneDocument(document) {
26226
+ return structuredClone(document);
26227
+ }
26228
+ function activeCredential(document) {
26229
+ const active = document.accounts.find((account) => account.id === document.activeAccountId);
26230
+ if (active === void 0) {
26231
+ throw new Error("active account missing from document");
26232
+ }
26233
+ return cloneCredential(active.credential);
26234
+ }
26235
+ function resolveAccountIdForCredential(credential) {
26236
+ const accountIdField = credential.accountId;
26237
+ if (isValidAccountId(accountIdField)) return accountIdField;
26238
+ return `acct-${randomBytes(8).toString("hex")}`;
26239
+ }
26240
+ function documentFromCredential(credential) {
26241
+ const id = resolveAccountIdForCredential(credential);
26242
+ return {
26243
+ version: AUTH_FORMAT_VERSION,
26244
+ activeAccountId: id,
26245
+ accounts: [
26246
+ {
26247
+ id,
26248
+ credential: cloneCredential(credential),
26249
+ createdAt: Date.now()
26250
+ }
26251
+ ]
26252
+ };
26253
+ }
26254
+ function withUpdatedActiveCredential(document, credential) {
26255
+ const accounts = document.accounts.map(
26256
+ (account) => account.id === document.activeAccountId ? { ...account, credential: cloneCredential(credential) } : cloneAccount(account)
26257
+ );
26258
+ return {
26259
+ version: AUTH_FORMAT_VERSION,
26260
+ activeAccountId: document.activeAccountId,
26261
+ accounts
26262
+ };
26263
+ }
26264
+ function cloneAccount(account) {
26265
+ return {
26266
+ id: account.id,
26267
+ ...account.label === void 0 ? {} : { label: account.label },
26268
+ credential: cloneCredential(account.credential),
26269
+ createdAt: account.createdAt
26270
+ };
26271
+ }
26272
+ function summarizeAccount(account) {
26273
+ const accountIdField = account.credential.accountId;
26274
+ return {
26275
+ id: account.id,
26276
+ ...account.label === void 0 ? {} : { label: account.label },
26277
+ expires: account.credential.expires,
26278
+ ...typeof accountIdField === "string" && accountIdField.length > 0 ? { accountId: accountIdField } : {}
26279
+ };
26280
+ }
26281
+ function validatedDocument(document, filename, label) {
26282
+ return parseDocument(JSON.stringify(document), filename, label).document;
26283
+ }
26125
26284
  function oauthCredentialPath(basename, dshHome) {
26126
26285
  if (basename.length === 0 || basename.length > 128 || basename === "." || basename === ".." || !/^\.?[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(basename)) {
26127
26286
  throw new TypeError("OAuth credential basename must be a safe local filename");
@@ -26140,6 +26299,7 @@ var OAuthCredentialFileStore = class {
26140
26299
  providerId;
26141
26300
  label;
26142
26301
  filename;
26302
+ loginPersist;
26143
26303
  async readCurrent() {
26144
26304
  return this.loadCurrent({ allowUnreadable: false });
26145
26305
  }
@@ -26149,6 +26309,10 @@ var OAuthCredentialFileStore = class {
26149
26309
  * Unsafe/symlink/wrong-owner/too-large files still throw.
26150
26310
  */
26151
26311
  async loadCurrent(options) {
26312
+ const loaded = await this.loadDocument({ allowUnreadable: options.allowUnreadable, persistMigration: false });
26313
+ return loaded === void 0 ? void 0 : activeCredential(loaded.document);
26314
+ }
26315
+ async loadDocument(options) {
26152
26316
  let text;
26153
26317
  try {
26154
26318
  text = (await readHardenedOAuthSourceFile(this.filename)).text;
@@ -26160,7 +26324,11 @@ var OAuthCredentialFileStore = class {
26160
26324
  throw error;
26161
26325
  }
26162
26326
  try {
26163
- return cloneCredential(parseDocument(text, this.filename, this.label).credential);
26327
+ const parsed = parseDocument(text, this.filename, this.label);
26328
+ if (options.persistMigration && parsed.migratedFromV1) {
26329
+ await this.writeDocument(parsed.document);
26330
+ }
26331
+ return parsed;
26164
26332
  } catch (error) {
26165
26333
  if (options.allowUnreadable) return void 0;
26166
26334
  throw error;
@@ -26178,6 +26346,91 @@ var OAuthCredentialFileStore = class {
26178
26346
  throw error;
26179
26347
  }
26180
26348
  }
26349
+ async writeDocument(document) {
26350
+ const validated = validatedDocument(document, this.filename, this.label);
26351
+ await this.assertDestinationReplaceable();
26352
+ await writeFileAtomic(this.filename, `${JSON.stringify(validated, null, 2)}
26353
+ `, {
26354
+ mode: 384,
26355
+ dirMode: 448
26356
+ });
26357
+ return cloneDocument(validated);
26358
+ }
26359
+ nextDocumentAfterLogin(current, credential, options) {
26360
+ const parsed = parseOAuthCredential(credential, this.filename, this.label);
26361
+ if (options.mode === "overwrite-active") {
26362
+ if (current === void 0) return documentFromCredential(parsed);
26363
+ if (options.confirmOverwrite !== true) {
26364
+ throw new Error(`${this.label}: confirmOverwrite is required to replace the active account`);
26365
+ }
26366
+ return withUpdatedActiveCredential(current, parsed);
26367
+ }
26368
+ const id = resolveAccountIdForCredential(parsed);
26369
+ if (current === void 0) {
26370
+ return {
26371
+ version: AUTH_FORMAT_VERSION,
26372
+ activeAccountId: id,
26373
+ accounts: [
26374
+ {
26375
+ id,
26376
+ credential: cloneCredential(parsed),
26377
+ createdAt: Date.now()
26378
+ }
26379
+ ]
26380
+ };
26381
+ }
26382
+ const existingIndex = current.accounts.findIndex((account) => account.id === id);
26383
+ if (existingIndex < 0 && current.accounts.length >= MAX_ACCOUNTS) {
26384
+ throw new Error(`${this.label}: at most ${String(MAX_ACCOUNTS)} accounts may be stored`);
26385
+ }
26386
+ const accounts = current.accounts.map(cloneAccount);
26387
+ if (existingIndex >= 0) {
26388
+ const existing = accounts[existingIndex];
26389
+ if (existing === void 0) {
26390
+ throw new Error(`${this.label}: account "${id}" is not stored`);
26391
+ }
26392
+ accounts[existingIndex] = {
26393
+ id,
26394
+ ...existing.label === void 0 ? {} : { label: existing.label },
26395
+ credential: cloneCredential(parsed),
26396
+ createdAt: existing.createdAt
26397
+ };
26398
+ } else {
26399
+ accounts.push({
26400
+ id,
26401
+ credential: cloneCredential(parsed),
26402
+ createdAt: Date.now()
26403
+ });
26404
+ }
26405
+ return {
26406
+ version: AUTH_FORMAT_VERSION,
26407
+ activeAccountId: id,
26408
+ accounts
26409
+ };
26410
+ }
26411
+ async mutateDocument(options, fn) {
26412
+ await mkdir(dirname(this.filename), { recursive: true, mode: 448 });
26413
+ return withFileLock(this.filename, async () => {
26414
+ const loaded = await this.loadDocument({
26415
+ allowUnreadable: options.allowUnreadable,
26416
+ persistMigration: false
26417
+ });
26418
+ const current = loaded === void 0 ? void 0 : loaded.document;
26419
+ const migratedFromV1 = loaded?.migratedFromV1 === true;
26420
+ const next = await fn(current === void 0 ? void 0 : cloneDocument(current));
26421
+ if (next === UNCHANGED) {
26422
+ if (migratedFromV1 && current !== void 0) {
26423
+ return this.writeDocument(current);
26424
+ }
26425
+ return current === void 0 ? void 0 : cloneDocument(current);
26426
+ }
26427
+ if (next === void 0) {
26428
+ await rm(this.filename, { force: true });
26429
+ return void 0;
26430
+ }
26431
+ return this.writeDocument(next);
26432
+ });
26433
+ }
26181
26434
  async read(providerId) {
26182
26435
  return providerId === this.providerId ? this.readCurrent() : void 0;
26183
26436
  }
@@ -26188,27 +26441,42 @@ var OAuthCredentialFileStore = class {
26188
26441
  if (providerId !== this.providerId) {
26189
26442
  throw new Error(`${this.label}: credential store does not own provider "${providerId}"`);
26190
26443
  }
26191
- await mkdir(dirname(this.filename), { recursive: true, mode: 448 });
26192
- return withFileLock(this.filename, async () => {
26193
- const current = await this.loadCurrent({ allowUnreadable: true });
26194
- const candidate = await fn(current);
26195
- if (candidate === void 0) return current;
26196
- const document = parseDocument(
26197
- JSON.stringify({
26198
- version: AUTH_FORMAT_VERSION,
26199
- credential: candidate
26200
- }),
26201
- this.filename,
26202
- this.label
26203
- );
26204
- await this.assertDestinationReplaceable();
26205
- await writeFileAtomic(this.filename, `${JSON.stringify(document, null, 2)}
26206
- `, {
26207
- mode: 384,
26208
- dirMode: 448
26209
- });
26210
- return cloneCredential(document.credential);
26444
+ const document = await this.mutateDocument({ allowUnreadable: true }, async (current) => {
26445
+ const active = current === void 0 ? void 0 : activeCredential(current);
26446
+ const candidate = await fn(active);
26447
+ if (candidate === void 0) return UNCHANGED;
26448
+ if (candidate.type !== "oauth") {
26449
+ throw new Error(`${this.label}: ${this.filename} credential type must be oauth`);
26450
+ }
26451
+ if (this.loginPersist !== void 0) {
26452
+ return this.nextDocumentAfterLogin(current, candidate, this.loginPersist);
26453
+ }
26454
+ if (current === void 0) return documentFromCredential(candidate);
26455
+ return withUpdatedActiveCredential(current, candidate);
26211
26456
  });
26457
+ return document === void 0 ? void 0 : activeCredential(document);
26458
+ }
26459
+ /**
26460
+ * Redirect `modify` writes (pi-ai `models.login`) through multi-account upsert
26461
+ * semantics for the duration of `fn`.
26462
+ */
26463
+ async runLoginPersist(options, fn) {
26464
+ if (this.loginPersist !== void 0) {
26465
+ throw new Error(`${this.label}: login persist already in progress`);
26466
+ }
26467
+ this.loginPersist = options;
26468
+ try {
26469
+ return await fn();
26470
+ } finally {
26471
+ this.loginPersist = void 0;
26472
+ }
26473
+ }
26474
+ /** Persist a login/import credential without going through pi-ai `modify`. */
26475
+ async persistLoginCredential(credential, options) {
26476
+ await this.mutateDocument(
26477
+ { allowUnreadable: true },
26478
+ async (current) => this.nextDocumentAfterLogin(current, credential, options)
26479
+ );
26212
26480
  }
26213
26481
  /**
26214
26482
  * Force the next `getAuth()` to refresh by backdating `expires` into the past.
@@ -26233,6 +26501,163 @@ var OAuthCredentialFileStore = class {
26233
26501
  await mkdir(dirname(this.filename), { recursive: true, mode: 448 });
26234
26502
  await withFileLock(this.filename, () => rm(this.filename, { force: true }));
26235
26503
  }
26504
+ /** Read-only account list for Settings / API. Never includes tokens. */
26505
+ async listAccounts() {
26506
+ const document = await this.mutateDocument({ allowUnreadable: false }, async (current) => {
26507
+ if (current === void 0) return UNCHANGED;
26508
+ return UNCHANGED;
26509
+ });
26510
+ if (document === void 0) return [];
26511
+ return document.accounts.map(summarizeAccount);
26512
+ }
26513
+ async getActiveAccountId() {
26514
+ const document = await this.mutateDocument({ allowUnreadable: false }, async (current) => {
26515
+ if (current === void 0) return UNCHANGED;
26516
+ return UNCHANGED;
26517
+ });
26518
+ return document?.activeAccountId;
26519
+ }
26520
+ /**
26521
+ * Read one account's credential without changing activeAccountId.
26522
+ * Used by the optional quota-aware pool proxy for sticky per-request routing.
26523
+ */
26524
+ async readAccount(id) {
26525
+ if (!isValidAccountId(id)) return void 0;
26526
+ const document = await this.mutateDocument({ allowUnreadable: false }, async (current) => {
26527
+ if (current === void 0) return UNCHANGED;
26528
+ return UNCHANGED;
26529
+ });
26530
+ const account = document?.accounts.find((entry) => entry.id === id);
26531
+ return account === void 0 ? void 0 : cloneCredential(account.credential);
26532
+ }
26533
+ /**
26534
+ * Serialized refresh/write for one account id. Does not move activeAccountId.
26535
+ * Returns undefined when the account is absent (caller treats as missing credential).
26536
+ */
26537
+ async modifyAccount(id, fn) {
26538
+ if (!isValidAccountId(id)) {
26539
+ throw new TypeError(`${this.label}: account id is invalid`);
26540
+ }
26541
+ const document = await this.mutateDocument({ allowUnreadable: true }, async (current) => {
26542
+ if (current === void 0) return UNCHANGED;
26543
+ const index = current.accounts.findIndex((account2) => account2.id === id);
26544
+ if (index < 0) return UNCHANGED;
26545
+ const existing = current.accounts[index];
26546
+ if (existing === void 0) return UNCHANGED;
26547
+ const candidate = await fn(cloneCredential(existing.credential));
26548
+ if (candidate === void 0) return UNCHANGED;
26549
+ if (candidate.type !== "oauth") {
26550
+ throw new Error(`${this.label}: ${this.filename} credential type must be oauth`);
26551
+ }
26552
+ const accounts = current.accounts.map(cloneAccount);
26553
+ const target = accounts[index];
26554
+ if (target === void 0) return UNCHANGED;
26555
+ accounts[index] = {
26556
+ ...target,
26557
+ credential: cloneCredential(candidate)
26558
+ };
26559
+ return {
26560
+ version: AUTH_FORMAT_VERSION,
26561
+ activeAccountId: current.activeAccountId,
26562
+ accounts
26563
+ };
26564
+ });
26565
+ if (document === void 0) return void 0;
26566
+ const account = document.accounts.find((entry) => entry.id === id);
26567
+ return account === void 0 ? void 0 : cloneCredential(account.credential);
26568
+ }
26569
+ async setActiveAccount(id) {
26570
+ if (!isValidAccountId(id)) {
26571
+ throw new TypeError(`${this.label}: account id is invalid`);
26572
+ }
26573
+ await this.mutateDocument({ allowUnreadable: false }, async (current) => {
26574
+ if (current === void 0) {
26575
+ throw new Error(`${this.label}: no credential accounts are stored`);
26576
+ }
26577
+ if (!current.accounts.some((account) => account.id === id)) {
26578
+ throw new Error(`${this.label}: account "${id}" is not stored`);
26579
+ }
26580
+ if (current.activeAccountId === id) return UNCHANGED;
26581
+ return {
26582
+ version: AUTH_FORMAT_VERSION,
26583
+ activeAccountId: id,
26584
+ accounts: current.accounts.map(cloneAccount)
26585
+ };
26586
+ });
26587
+ }
26588
+ async upsertAccount(input) {
26589
+ if (!isValidAccountId(input.id)) {
26590
+ throw new TypeError(`${this.label}: account id is invalid`);
26591
+ }
26592
+ if (input.label !== void 0 && (typeof input.label !== "string" || input.label.length === 0)) {
26593
+ throw new TypeError(`${this.label}: account label must be a non-empty string when present`);
26594
+ }
26595
+ const credential = parseOAuthCredential(input.credential, this.filename, this.label);
26596
+ await this.mutateDocument({ allowUnreadable: false }, async (current) => {
26597
+ if (current === void 0) {
26598
+ return {
26599
+ version: AUTH_FORMAT_VERSION,
26600
+ activeAccountId: input.id,
26601
+ accounts: [
26602
+ {
26603
+ id: input.id,
26604
+ ...input.label === void 0 ? {} : { label: input.label },
26605
+ credential: cloneCredential(credential),
26606
+ createdAt: Date.now()
26607
+ }
26608
+ ]
26609
+ };
26610
+ }
26611
+ const existingIndex = current.accounts.findIndex((account) => account.id === input.id);
26612
+ if (existingIndex < 0 && current.accounts.length >= MAX_ACCOUNTS) {
26613
+ throw new Error(`${this.label}: at most ${String(MAX_ACCOUNTS)} accounts may be stored`);
26614
+ }
26615
+ const accounts = current.accounts.map(cloneAccount);
26616
+ if (existingIndex >= 0) {
26617
+ const existing = accounts[existingIndex];
26618
+ if (existing === void 0) {
26619
+ throw new Error(`${this.label}: account "${input.id}" is not stored`);
26620
+ }
26621
+ accounts[existingIndex] = {
26622
+ id: input.id,
26623
+ ...input.label !== void 0 ? { label: input.label } : existing.label === void 0 ? {} : { label: existing.label },
26624
+ credential: cloneCredential(credential),
26625
+ createdAt: existing.createdAt
26626
+ };
26627
+ } else {
26628
+ accounts.push({
26629
+ id: input.id,
26630
+ ...input.label === void 0 ? {} : { label: input.label },
26631
+ credential: cloneCredential(credential),
26632
+ createdAt: Date.now()
26633
+ });
26634
+ }
26635
+ return {
26636
+ version: AUTH_FORMAT_VERSION,
26637
+ activeAccountId: input.makeActive === true ? input.id : current.activeAccountId,
26638
+ accounts
26639
+ };
26640
+ });
26641
+ }
26642
+ async removeAccount(id) {
26643
+ if (!isValidAccountId(id)) {
26644
+ throw new TypeError(`${this.label}: account id is invalid`);
26645
+ }
26646
+ await this.mutateDocument({ allowUnreadable: false }, async (current) => {
26647
+ if (current === void 0) return UNCHANGED;
26648
+ const accounts = current.accounts.filter((account) => account.id !== id).map(cloneAccount);
26649
+ if (accounts.length === current.accounts.length) return UNCHANGED;
26650
+ if (accounts.length === 0) return void 0;
26651
+ const fallback = accounts[0];
26652
+ if (fallback === void 0) return void 0;
26653
+ const activeAccountId = current.activeAccountId === id ? fallback.id : current.activeAccountId;
26654
+ return {
26655
+ version: AUTH_FORMAT_VERSION,
26656
+ activeAccountId,
26657
+ accounts
26658
+ };
26659
+ });
26660
+ }
26236
26661
  };
26237
26662
  var GrokBuildCredentialStore = class extends OAuthCredentialFileStore {
26238
26663
  constructor(filename = grokBuildAuthPath()) {
@@ -26325,7 +26750,7 @@ var GATEWAY_TOS_WARNING = "local API gateway is enabled; exposing a subscription
26325
26750
 
26326
26751
  // src/oauth.ts
26327
26752
  import { Buffer as Buffer2 } from "node:buffer";
26328
- import { createHash, randomBytes } from "node:crypto";
26753
+ import { createHash, randomBytes as randomBytes2 } from "node:crypto";
26329
26754
  import { createServer } from "node:http";
26330
26755
  var GROK_BUILD_OAUTH_ISSUER = "https://auth.x.ai";
26331
26756
  var APPROVED_GROK_ISSUER_ORIGIN2 = "https://auth.x.ai";
@@ -26492,12 +26917,12 @@ async function discoverOAuthEndpoints(issuer, signal, options = {}) {
26492
26917
  return parsed;
26493
26918
  }
26494
26919
  function generatePkce() {
26495
- const verifier = randomBytes(32).toString("base64url");
26920
+ const verifier = randomBytes2(32).toString("base64url");
26496
26921
  const challenge = createHash("sha256").update(verifier).digest("base64url");
26497
26922
  return { verifier, challenge };
26498
26923
  }
26499
26924
  function randomToken() {
26500
- return randomBytes(16).toString("base64url");
26925
+ return randomBytes2(16).toString("base64url");
26501
26926
  }
26502
26927
  function buildAuthorizeUrl(endpoints, params, redirectUri, challenge, state, nonce) {
26503
26928
  const url = new URL(endpoints.authorization_endpoint);
@@ -26931,6 +27356,10 @@ var OAuthProviderSession = class {
26931
27356
  async invalidateAccessToken() {
26932
27357
  await this.store.invalidate(this.definition.nativeProviderId);
26933
27358
  }
27359
+ /** Refresh host discovery after an account switch/remove without a full logout. */
27360
+ notifyCredentialChange() {
27361
+ this.onCatalogChange?.();
27362
+ }
26934
27363
  async storedCredential() {
26935
27364
  const credential = await this.store.read(this.definition.nativeProviderId);
26936
27365
  return credential?.type === "oauth" ? credential : void 0;
@@ -27363,6 +27792,10 @@ var GrokBuildSession = class {
27363
27792
  async invalidateAccessToken() {
27364
27793
  await this.store.invalidate(XAI_PI_PROVIDER);
27365
27794
  }
27795
+ /** Refresh host discovery after an account switch/remove without a full logout. */
27796
+ notifyCredentialChange() {
27797
+ this.onCatalogChange?.();
27798
+ }
27366
27799
  async logout() {
27367
27800
  try {
27368
27801
  await this.store.delete(XAI_PI_PROVIDER);