dsh-grok-subscription 0.1.3 → 0.1.13

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 (4) hide show
  1. package/README.md +79 -3
  2. package/lib/client.js +613 -49
  3. package/lib/index.js +914 -309
  4. package/package.json +21 -2
package/lib/index.js CHANGED
@@ -8,6 +8,7 @@ var CREDENTIAL_REF_NAME = "GROK_BUILD_ACCESS_TOKEN";
8
8
  var PROXY_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
9
9
  var RESPONSES_URL = "https://cli-chat-proxy.grok.com/v1/responses";
10
10
  var MODELS_V2_URL = "https://cli-chat-proxy.grok.com/v1/models-v2";
11
+ var BILLING_CREDITS_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
11
12
  var TOKEN_AUTH_HEADER = "X-XAI-Token-Auth";
12
13
  var TOKEN_AUTH_VALUE = "xai-grok-cli";
13
14
  var CLIENT_IDENTIFIER_HEADER = "x-grok-client-identifier";
@@ -19,11 +20,16 @@ var SESSION_AUTH_MODES = Object.freeze(["oidc", "external", "web_login", "grok"]
19
20
  var API_KEY_AUTH_MODES = Object.freeze(["api_key"]);
20
21
  var AUTH_FILE_MAX_BYTES = 1048576;
21
22
  var CATALOG_TIMEOUT_MS = 15e3;
23
+ var USAGE_TIMEOUT_MS = 8e3;
24
+ var STORE_TOKEN_TIMEOUT_MS = 5e3;
22
25
  var STREAM_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
23
26
  var MAX_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024;
24
27
  var REQUEST_IMAGE_PIXEL_BUDGET = 2048 * 2048;
25
28
  var REQUEST_IMAGE_MAX_BYTES = 1024 * 1024;
26
29
  var FALLBACK_MODEL_IDS = Object.freeze(["grok-4.7", "grok-4.6", "grok-4.5"]);
30
+ var IMPORT_TIMEOUT_MS = 2500;
31
+ var CREDENTIALS_IO_TIMEOUT_MS = 1500;
32
+ var RPC_HANDLER_TIMEOUT_MS = 8e3;
27
33
 
28
34
  // src/session.js
29
35
  import { existsSync as existsSync2 } from "node:fs";
@@ -401,273 +407,312 @@ async function loadCatalog(accessToken, options = {}) {
401
407
  }
402
408
  }
403
409
 
404
- // src/session.js
405
- function resolveGrokBin(env = process.env, exists = existsSync2) {
406
- if (typeof env.DSH_GROK_BIN === "string" && env.DSH_GROK_BIN.trim()) return env.DSH_GROK_BIN.trim();
407
- const local = join2(grokHome(env), "bin", "grok");
408
- if (exists(local)) return local;
409
- return "grok";
410
+ // src/usage.js
411
+ var USAGE_SOURCE = "billing-credits-undocumented";
412
+ var SHANGHAI_TZ = "Asia/Shanghai";
413
+ function unavailable(reason) {
414
+ return Object.freeze({
415
+ status: "unavailable",
416
+ reason: typeof reason === "string" && reason.trim() ? reason.trim() : "Usage unavailable",
417
+ experimental: true,
418
+ source: USAGE_SOURCE
419
+ });
410
420
  }
411
- function grokCliAvailable(env = process.env, exists = existsSync2) {
412
- const bin = resolveGrokBin(env, exists);
413
- if (bin.includes("/") || bin.includes("\\")) return exists(bin);
414
- const delimiter = process.platform === "win32" ? ";" : ":";
415
- return String(env.PATH ?? "").split(delimiter).some((dir) => dir && exists(join2(dir, bin)));
421
+ function asFiniteNumber(value) {
422
+ if (typeof value === "number" && Number.isFinite(value)) return value;
423
+ if (typeof value === "string" && value.trim()) {
424
+ const number = Number(value);
425
+ if (Number.isFinite(number)) return number;
426
+ }
427
+ if (value && typeof value === "object" && !Array.isArray(value) && "val" in value) {
428
+ return asFiniteNumber(value.val);
429
+ }
430
+ return void 0;
416
431
  }
417
- function spawnGrokLogin(options = {}) {
418
- const spawnFn = options.spawn ?? spawn;
419
- const bin = resolveGrokBin(options.env, options.exists);
420
- const args = options.device ? ["login", "--device-auth"] : ["login"];
421
- return new Promise((resolve, reject) => {
422
- const child = spawnFn(bin, args, {
423
- stdio: options.stdio ?? "inherit",
424
- env: options.env ?? process.env
425
- });
426
- child.on("error", (error) => {
427
- reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
428
- });
429
- child.on("exit", (code, signal) => {
430
- if (code === 0) resolve({ ok: true, code });
431
- else reject(new Error(signal ? `grok login terminated by ${signal}` : `grok login exited with code ${code ?? "unknown"}`));
432
- });
433
- });
432
+ function asFinitePercent(value) {
433
+ return asFiniteNumber(value);
434
+ }
435
+ function asIsoString(value) {
436
+ if (typeof value !== "string" || !value.trim()) return void 0;
437
+ const trimmed = value.trim();
438
+ const ms = Date.parse(trimmed);
439
+ if (!Number.isFinite(ms)) return void 0;
440
+ return new Date(ms).toISOString();
434
441
  }
435
- async function credentialRefOf() {
442
+ function formatShanghai(iso) {
443
+ if (!iso) return void 0;
436
444
  try {
437
- const mod = await import("@deepseek-ai/dsh-credentials");
438
- if (typeof mod.credentialRef === "function") return mod.credentialRef(CREDENTIAL_REF_NAME);
445
+ return new Intl.DateTimeFormat("zh-CN", {
446
+ timeZone: SHANGHAI_TZ,
447
+ year: "numeric",
448
+ month: "2-digit",
449
+ day: "2-digit",
450
+ hour: "2-digit",
451
+ minute: "2-digit",
452
+ second: "2-digit",
453
+ hour12: false
454
+ }).format(new Date(iso));
439
455
  } catch {
456
+ return void 0;
440
457
  }
441
- return CREDENTIAL_REF_NAME;
442
458
  }
443
- function createSessionService({ credentials, logger, onCatalogChange } = {}) {
444
- let catalog = { models: [], source: "signed-out", error: void 0 };
445
- let lastPublic = publicSessionView(void 0);
446
- const notifyCatalogChange = () => {
447
- try {
448
- onCatalogChange?.();
449
- } catch (error) {
450
- logger?.warn?.(
451
- "Grok subscription catalog change notify failed: %s",
452
- error instanceof Error ? error.message : "unknown"
453
- );
454
- }
455
- };
456
- const readStoredToken = async () => {
457
- if (!credentials?.resolve) return void 0;
458
- const hit = await credentials.resolve(await credentialRefOf());
459
- const value = hit?.value;
460
- return typeof value === "string" && value.length > 0 ? value : void 0;
461
- };
462
- const storeToken = async (token) => {
463
- if (!credentials?.set) throw new Error("DSH credentials service is unavailable");
464
- await credentials.set(await credentialRefOf(), token);
465
- };
466
- const clearToken = async () => {
467
- if (!credentials?.unset) return;
468
- await credentials.unset(await credentialRefOf());
469
- };
470
- const pull = async () => {
471
- const parsed = readGrokAuthSession();
472
- if (!parsed.session) {
473
- catalog = { models: [], source: "signed-out", error: void 0 };
474
- lastPublic = publicSessionView(void 0);
475
- await clearToken();
476
- const message = parsed.reason === "api-key-only" ? "Found an API-key entry only. Sign in with SuperGrok / X Premium via grok login." : "No Grok Build subscription session in auth.json. Run grok login first.";
477
- notifyCatalogChange();
478
- return { ok: false, error: message, account: lastPublic, catalog };
479
- }
480
- await storeToken(parsed.session.accessToken);
481
- lastPublic = publicSessionView(parsed.session);
482
- catalog = await loadCatalog(parsed.session.accessToken);
483
- notifyCatalogChange();
484
- return { ok: true, account: lastPublic, catalog };
485
- };
486
- const status = async () => {
487
- let token;
488
- try {
489
- token = await readStoredToken();
490
- } catch (error) {
491
- logger?.warn?.("could not resolve Grok subscription credential: %s", error instanceof Error ? error.message : "unknown");
492
- token = void 0;
493
- }
494
- if (!token) {
495
- lastPublic = publicSessionView(void 0);
496
- catalog = { models: [], source: "signed-out", error: void 0 };
497
- return { account: lastPublic, catalog, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
498
- }
499
- if (lastPublic.signedIn !== true) {
500
- try {
501
- const parsed = readGrokAuthSession();
502
- if (parsed.session) lastPublic = publicSessionView(parsed.session);
503
- else lastPublic = Object.freeze({ signedIn: true, maskedAccount: "\u2022\u2022\u2022\u2022", authMode: "oidc", source: "dsh-credentials" });
504
- } catch {
505
- lastPublic = Object.freeze({ signedIn: true, maskedAccount: "\u2022\u2022\u2022\u2022", authMode: "oidc", source: "dsh-credentials" });
506
- }
507
- }
508
- return { account: lastPublic, catalog, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
509
- };
510
- const refreshCatalog = async () => {
511
- const token = await readStoredToken();
512
- if (!token) {
513
- catalog = { models: [], source: "signed-out", error: void 0 };
514
- notifyCatalogChange();
515
- return catalog;
516
- }
517
- catalog = await loadCatalog(token);
518
- notifyCatalogChange();
519
- return catalog;
520
- };
521
- const login = async (options) => {
522
- await spawnGrokLogin(options);
523
- return pull();
524
- };
525
- const logout = async () => {
526
- await clearToken();
527
- catalog = { models: [], source: "signed-out", error: void 0 };
528
- lastPublic = publicSessionView(void 0);
529
- notifyCatalogChange();
530
- return { ok: true, account: lastPublic, catalog };
531
- };
532
- return {
533
- pull,
534
- status,
535
- refreshCatalog,
536
- login,
537
- logout,
538
- currentToken: readStoredToken,
539
- models: () => catalog.models,
540
- catalog: () => catalog,
541
- publicAccount: () => lastPublic
542
- };
459
+ function isPlainObject(value) {
460
+ return value !== null && typeof value === "object" && !Array.isArray(value);
543
461
  }
544
-
545
- // src/rpc-contract.js
546
- var RPC_ENDPOINTS = Object.freeze([
547
- "status",
548
- "login/cli",
549
- "login/device",
550
- "pull",
551
- "logout",
552
- "catalog/refresh"
553
- ]);
554
- function publicResult(value) {
555
- return { ok: true, value };
462
+ function topLevelKeys(body) {
463
+ return Object.keys(body).filter((key) => typeof key === "string").slice(0, 24);
556
464
  }
557
- function publicError(error, fallback = "Grok subscription request failed") {
558
- const message = error instanceof Error ? error.message : fallback;
559
- return { ok: false, error: { code: "internal", message, details: { issues: [] } } };
465
+ function missingPercentReason(body) {
466
+ const keys = topLevelKeys(body);
467
+ if (keys.length === 0) {
468
+ return "Missing creditUsagePercent (empty object)";
469
+ }
470
+ return `Missing creditUsagePercent (keys: ${keys.join(",")})`;
560
471
  }
561
-
562
- // src/rpc.js
563
- function stripSecrets(value) {
564
- if (!value || typeof value !== "object") return value;
565
- const next = { ...value };
566
- delete next.accessToken;
567
- delete next.token;
568
- delete next.refresh;
569
- delete next.refresh_token;
570
- delete next.key;
571
- if (next.catalog) {
572
- next.catalog = {
573
- source: next.catalog.source,
574
- error: next.catalog.error,
575
- models: Array.isArray(next.catalog.models) ? next.catalog.models.map((model) => ({
576
- id: model.id,
577
- name: model.name,
578
- contextWindow: model.contextWindow,
579
- reasoning: model.reasoning,
580
- source: model.source
581
- })) : []
582
- };
472
+ function firstDefined(...values) {
473
+ for (const value of values) {
474
+ if (value !== void 0 && value !== null) return value;
583
475
  }
584
- if (next.account) {
585
- next.account = {
586
- signedIn: next.account.signedIn === true,
587
- maskedAccount: next.account.maskedAccount,
588
- authMode: next.account.authMode,
589
- expiresAt: next.account.expiresAt,
590
- source: next.account.source
591
- };
476
+ return void 0;
477
+ }
478
+ function readPercentCandidate(node) {
479
+ if (!isPlainObject(node)) return void 0;
480
+ return asFinitePercent(firstDefined(
481
+ node.creditUsagePercent,
482
+ node.usedPercent,
483
+ node.usagePercent,
484
+ node.percentUsed,
485
+ node.used_percent,
486
+ node.usage_percent
487
+ ));
488
+ }
489
+ function resolveUsedPercent(body) {
490
+ const config = isPlainObject(body.config) ? body.config : void 0;
491
+ const usage = isPlainObject(body.usage) ? body.usage : void 0;
492
+ const credits = isPlainObject(body.credits) ? body.credits : void 0;
493
+ const direct = firstDefined(
494
+ readPercentCandidate(body),
495
+ readPercentCandidate(config),
496
+ readPercentCandidate(usage),
497
+ readPercentCandidate(credits),
498
+ asFinitePercent(credits?.usedPercent)
499
+ );
500
+ if (direct !== void 0) return direct;
501
+ const used = firstDefined(
502
+ asFiniteNumber(body.used),
503
+ asFiniteNumber(body.includedUsed),
504
+ asFiniteNumber(body.totalUsed),
505
+ asFiniteNumber(config?.includedUsed),
506
+ asFiniteNumber(config?.totalUsed),
507
+ asFiniteNumber(usage?.used),
508
+ asFiniteNumber(credits?.used)
509
+ );
510
+ const total = firstDefined(
511
+ asFiniteNumber(body.total),
512
+ asFiniteNumber(body.monthlyLimit),
513
+ asFiniteNumber(config?.monthlyLimit),
514
+ asFiniteNumber(usage?.total),
515
+ asFiniteNumber(credits?.total),
516
+ asFiniteNumber(credits?.limit)
517
+ );
518
+ if (used !== void 0 && total !== void 0 && total > 0) {
519
+ return used / total * 100;
592
520
  }
593
- return next;
521
+ return void 0;
594
522
  }
595
- function createRpcHandler(session) {
596
- return async function handle(endpoint, _payload, _signal) {
597
- try {
598
- if (endpoint === "status") return publicResult(stripSecrets(await session.status()));
599
- if (endpoint === "pull") return publicResult(stripSecrets(await session.pull()));
600
- if (endpoint === "logout") return publicResult(stripSecrets(await session.logout()));
601
- if (endpoint === "catalog/refresh") {
602
- const catalog = await session.refreshCatalog();
603
- return publicResult(stripSecrets({ catalog, account: session.publicAccount() }));
604
- }
605
- if (endpoint === "login/cli") return publicResult(stripSecrets(await session.login({ device: false })));
606
- if (endpoint === "login/device") return publicResult(stripSecrets(await session.login({ device: true })));
607
- return publicError(new Error(`Unknown Grok subscription RPC: ${endpoint}`));
608
- } catch (error) {
609
- return publicError(error);
610
- }
611
- };
523
+ function resolvePeriodBounds(body) {
524
+ const config = isPlainObject(body.config) ? body.config : void 0;
525
+ const period = firstDefined(
526
+ isPlainObject(config?.currentPeriod) ? config.currentPeriod : void 0,
527
+ isPlainObject(body.currentPeriod) ? body.currentPeriod : void 0,
528
+ isPlainObject(body.period) ? body.period : void 0
529
+ );
530
+ const start = firstDefined(
531
+ period ? asIsoString(period.start) : void 0,
532
+ asIsoString(config?.billingPeriodStart),
533
+ asIsoString(body.billingPeriodStart),
534
+ asIsoString(body.periodStart),
535
+ asIsoString(body.resetStart)
536
+ );
537
+ const end = firstDefined(
538
+ period ? asIsoString(period.end) : void 0,
539
+ asIsoString(config?.billingPeriodEnd),
540
+ asIsoString(body.billingPeriodEnd),
541
+ asIsoString(body.periodEnd),
542
+ asIsoString(body.resetAt),
543
+ asIsoString(body.reset_at)
544
+ );
545
+ return { start, end };
612
546
  }
613
-
614
- // src/transport.js
615
- function parseEnvelope(body, method) {
616
- if (!body || typeof body !== "object") return void 0;
617
- if (body.method !== method) return void 0;
618
- return {
619
- rpcId: body.rpcId,
620
- payload: body.payload
621
- };
547
+ function resolveProductUsage(body) {
548
+ const config = isPlainObject(body.config) ? body.config : void 0;
549
+ return firstDefined(
550
+ Array.isArray(body.productUsage) ? body.productUsage : void 0,
551
+ Array.isArray(config?.productUsage) ? config.productUsage : void 0,
552
+ Array.isArray(body.products) ? body.products : void 0
553
+ );
622
554
  }
623
- function registerSubscriptionTransport(connection, handler) {
624
- const disposers = [];
555
+ function sanitizeProductUsage(raw) {
556
+ if (!Array.isArray(raw)) return void 0;
557
+ const rows = [];
558
+ for (const item of raw) {
559
+ if (!item || typeof item !== "object") continue;
560
+ const name2 = typeof item.name === "string" && item.name.trim() ? item.name.trim() : typeof item.product === "string" && item.product.trim() ? item.product.trim() : typeof item.id === "string" && item.id.trim() ? item.id.trim() : void 0;
561
+ const usedPercent = asFinitePercent(firstDefined(
562
+ item.creditUsagePercent,
563
+ item.usedPercent,
564
+ item.usagePercent,
565
+ item.percentUsed,
566
+ item.used_percent
567
+ ));
568
+ if (!name2 && usedPercent === void 0) continue;
569
+ rows.push(Object.freeze({
570
+ ...name2 ? { name: name2 } : {},
571
+ ...usedPercent !== void 0 ? { usedPercent } : {}
572
+ }));
573
+ }
574
+ return rows.length ? Object.freeze(rows) : void 0;
575
+ }
576
+ function parseBillingCredits(body) {
577
+ if (body === null || body === void 0) {
578
+ return unavailable("Empty billing response");
579
+ }
580
+ if (typeof body !== "object" || Array.isArray(body)) {
581
+ return unavailable("Unexpected billing response shape");
582
+ }
583
+ const usedPercent = resolveUsedPercent(body);
584
+ if (usedPercent === void 0) {
585
+ return unavailable(missingPercentReason(body));
586
+ }
587
+ const { start: periodStart, end: periodEnd } = resolvePeriodBounds(body);
588
+ const productUsage = sanitizeProductUsage(resolveProductUsage(body));
589
+ const remainingPercent = Math.max(0, 100 - usedPercent);
590
+ return Object.freeze({
591
+ status: "ok",
592
+ experimental: true,
593
+ source: USAGE_SOURCE,
594
+ usedPercent,
595
+ remainingPercent,
596
+ ...periodStart ? { periodStart, periodStartLocal: formatShanghai(periodStart) } : {},
597
+ ...periodEnd ? { periodEnd, periodEndLocal: formatShanghai(periodEnd) } : {},
598
+ ...productUsage ? { productUsage } : {}
599
+ });
600
+ }
601
+ function parseBillingCreditsJson(text) {
602
+ if (typeof text !== "string") return unavailable("Non-text billing response");
603
+ if (!text.trim()) return unavailable("Empty billing response");
604
+ let body;
625
605
  try {
626
- for (const endpoint of RPC_ENDPOINTS) {
627
- const method = `grok-subscription/${endpoint}`;
628
- if (typeof connection?.fetch?.register !== "function") {
629
- throw new Error("DSH connection.fetch.register is unavailable");
630
- }
631
- disposers.push(connection.fetch.register({
632
- path: `/api/${method}`,
633
- methods: ["POST"],
634
- requestBody: "buffered",
635
- async fetch(request) {
636
- if (request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") {
637
- return new Response("content type must be application/json", { status: 415 });
638
- }
639
- let body;
640
- try {
641
- body = await request.json();
642
- } catch {
643
- return new Response("invalid JSON", { status: 400 });
644
- }
645
- const envelope = parseEnvelope(body, method);
646
- if (!envelope) return new Response("invalid RPC envelope", { status: 400 });
647
- let result;
648
- try {
649
- request.signal?.throwIfAborted?.();
650
- result = await handler(endpoint, envelope.payload, request.signal);
651
- } catch {
652
- result = { ok: false, error: { code: "internal", message: "Grok subscription request failed", details: { issues: [] } } };
653
- }
654
- return Response.json({ type: "server-response", rpcId: envelope.rpcId, result });
655
- }
656
- }));
606
+ body = JSON.parse(text);
607
+ } catch {
608
+ return unavailable("Invalid JSON from billing API");
609
+ }
610
+ return parseBillingCredits(body);
611
+ }
612
+ async function fetchBillingUsageOnce(accessToken, options = {}) {
613
+ const fetchImpl = options.fetch ?? globalThis.fetch;
614
+ if (typeof fetchImpl !== "function") {
615
+ return unavailable("Fetch unavailable");
616
+ }
617
+ let response;
618
+ try {
619
+ const signal = options.signal ?? AbortSignal.timeout(options.timeoutMs ?? USAGE_TIMEOUT_MS);
620
+ response = await fetchImpl(BILLING_CREDITS_URL, {
621
+ method: "GET",
622
+ headers: {
623
+ Accept: "application/json",
624
+ ...buildProxyHeaders(accessToken, options)
625
+ },
626
+ redirect: "error",
627
+ signal
628
+ });
629
+ } catch (error) {
630
+ const message = error instanceof Error ? error.message : "network error";
631
+ if (/abort|timeout/i.test(message)) return unavailable("Billing request timed out");
632
+ return unavailable("Billing network error");
633
+ }
634
+ if (response.status === 401 || response.status === 403) {
635
+ return unavailable(`Billing unauthorized (HTTP ${response.status})`);
636
+ }
637
+ if (!response.ok) {
638
+ return unavailable(`Billing HTTP ${response.status}`);
639
+ }
640
+ const rawContentType = typeof response.headers?.get === "function" ? response.headers.get("content-type") ?? "" : "";
641
+ const contentType = String(rawContentType).split(";")[0].trim();
642
+ const contentTypeLower = contentType.toLowerCase();
643
+ const looksJson = !contentTypeLower || contentTypeLower.includes("json") || contentTypeLower === "text/plain";
644
+ let text;
645
+ try {
646
+ text = await response.text();
647
+ } catch {
648
+ return unavailable("Could not read billing body");
649
+ }
650
+ if (!looksJson) {
651
+ const parsedAnyway = parseBillingCreditsJson(text);
652
+ if (parsedAnyway.status === "ok") {
653
+ return Object.freeze({
654
+ ...parsedAnyway,
655
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
656
+ });
657
657
  }
658
+ return unavailable(`Non-JSON billing Content-Type: ${contentType || "unknown"}`);
659
+ }
660
+ const parsed = parseBillingCreditsJson(text);
661
+ if (parsed.status === "ok") {
662
+ return Object.freeze({
663
+ ...parsed,
664
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
665
+ });
666
+ }
667
+ return parsed;
668
+ }
669
+ async function fetchBillingUsage(accessToken, options = {}) {
670
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
671
+ return unavailable("Not signed in");
672
+ }
673
+ const timeoutMs = options.timeoutMs ?? USAGE_TIMEOUT_MS;
674
+ let timer;
675
+ try {
676
+ const result = await Promise.race([
677
+ fetchBillingUsageOnce(accessToken, options),
678
+ new Promise((resolve) => {
679
+ timer = setTimeout(
680
+ () => resolve(unavailable("Billing request timed out")),
681
+ timeoutMs
682
+ );
683
+ })
684
+ ]);
685
+ return result;
658
686
  } catch (error) {
659
- for (const dispose of disposers.reverse()) dispose?.();
660
- throw error;
687
+ const message = error instanceof Error ? error.message : "network error";
688
+ if (/abort|timeout/i.test(message)) return unavailable("Billing request timed out");
689
+ return unavailable("Billing network error");
690
+ } finally {
691
+ if (timer) clearTimeout(timer);
661
692
  }
662
- return () => {
663
- for (const dispose of disposers.reverse()) dispose?.();
664
- };
665
693
  }
666
694
 
667
695
  // src/adapter.js
668
- async function optionalImport(specifier) {
696
+ function withImportTimeout(promise, ms, message) {
697
+ let timer;
698
+ return Promise.race([
699
+ promise,
700
+ new Promise((_, reject) => {
701
+ timer = setTimeout(() => reject(new Error(message)), ms);
702
+ })
703
+ ]).finally(() => {
704
+ if (timer) clearTimeout(timer);
705
+ });
706
+ }
707
+ async function optionalImport(specifier, options = {}) {
708
+ const timeoutMs = typeof options.timeoutMs === "number" && options.timeoutMs > 0 ? options.timeoutMs : IMPORT_TIMEOUT_MS;
709
+ const importFn = typeof options.importFn === "function" ? options.importFn : (id) => import(id);
669
710
  try {
670
- return await import(specifier);
711
+ return await withImportTimeout(
712
+ Promise.resolve(importFn(specifier)),
713
+ timeoutMs,
714
+ `Import timed out after ${timeoutMs}ms: ${specifier}`
715
+ );
671
716
  } catch {
672
717
  return void 0;
673
718
  }
@@ -987,37 +1032,48 @@ function withEncryptedReasoningInclude(api) {
987
1032
  streamSimple: (model, context, options) => api.streamSimple(model, context, inject2(options))
988
1033
  };
989
1034
  }
990
- async function createGrokBuildAdapter(session) {
991
- const [piAi, dshPi, dshLlm] = await Promise.all([
992
- optionalImport("@earendil-works/pi-ai"),
993
- optionalImport("@deepseek-ai/dsh-llm-pi-ai"),
994
- optionalImport("@deepseek-ai/dsh-llm")
995
- ]);
996
- const duck = createDuckAdapter(session);
997
- const asHostAdapter = (candidate) => {
998
- if (!dshLlm?.LlmAdapter || candidate instanceof dshLlm.LlmAdapter) return candidate;
999
- class GrokBuildAdapter extends dshLlm.LlmAdapter {
1000
- providerInfo(provider) {
1001
- return candidate.providerInfo(provider);
1002
- }
1003
- providerRetryPolicy(provider) {
1004
- return candidate.providerRetryPolicy(provider);
1005
- }
1006
- listModels(provider) {
1007
- return candidate.listModels(provider);
1008
- }
1009
- resolveModel(provider, model, signal) {
1010
- return candidate.resolveModel(provider, model, signal);
1011
- }
1012
- prepareCall(provider, model, signal) {
1013
- return candidate.prepareCall(provider, model, signal);
1014
- }
1015
- stream(options) {
1016
- return candidate.stream(options);
1017
- }
1035
+ function wrapAsHostAdapter(candidate, dshLlm) {
1036
+ if (!dshLlm?.LlmAdapter || candidate instanceof dshLlm.LlmAdapter) return candidate;
1037
+ class GrokBuildAdapter extends dshLlm.LlmAdapter {
1038
+ providerInfo(provider) {
1039
+ return candidate.providerInfo(provider);
1040
+ }
1041
+ providerRetryPolicy(provider) {
1042
+ return candidate.providerRetryPolicy(provider);
1043
+ }
1044
+ listModels(provider) {
1045
+ return candidate.listModels(provider);
1046
+ }
1047
+ resolveModel(provider, model, signal) {
1048
+ return candidate.resolveModel(provider, model, signal);
1049
+ }
1050
+ prepareCall(provider, model, signal) {
1051
+ return candidate.prepareCall(provider, model, signal);
1018
1052
  }
1019
- return new GrokBuildAdapter();
1053
+ stream(options) {
1054
+ return candidate.stream(options);
1055
+ }
1056
+ }
1057
+ return new GrokBuildAdapter();
1058
+ }
1059
+ function createGrokBuildAdapterSync(session, options = {}) {
1060
+ const duck = createDuckAdapter(session);
1061
+ const dshLlm = options.dshLlm;
1062
+ return {
1063
+ adapter: wrapAsHostAdapter(duck, dshLlm),
1064
+ kind: "custom-mvp",
1065
+ note: "Registered a sync duck Responses adapter; pi-ai upgrade may follow in the background."
1020
1066
  };
1067
+ }
1068
+ async function createGrokBuildAdapter(session, options = {}) {
1069
+ const importOpts = options.importOptions ?? {};
1070
+ const [piAi, dshPi, dshLlm] = await Promise.all([
1071
+ optionalImport("@earendil-works/pi-ai", importOpts),
1072
+ optionalImport("@deepseek-ai/dsh-llm-pi-ai", importOpts),
1073
+ optionalImport("@deepseek-ai/dsh-llm", importOpts)
1074
+ ]);
1075
+ const duck = createDuckAdapter(session);
1076
+ const asHostAdapter = (candidate) => wrapAsHostAdapter(candidate, dshLlm);
1021
1077
  if (!piAi?.createProvider || !dshPi?.PiAiAdapter) {
1022
1078
  return {
1023
1079
  adapter: asHostAdapter(duck),
@@ -1027,8 +1083,8 @@ async function createGrokBuildAdapter(session) {
1027
1083
  }
1028
1084
  let responsesApi;
1029
1085
  try {
1030
- const lazy = await import("@earendil-works/pi-ai/api/openai-responses.lazy");
1031
- responsesApi = typeof lazy.openAIResponsesApi === "function" ? lazy.openAIResponsesApi() : void 0;
1086
+ const lazy = await optionalImport("@earendil-works/pi-ai/api/openai-responses.lazy", importOpts);
1087
+ responsesApi = typeof lazy?.openAIResponsesApi === "function" ? lazy.openAIResponsesApi() : void 0;
1032
1088
  } catch {
1033
1089
  responsesApi = void 0;
1034
1090
  }
@@ -1120,10 +1176,513 @@ async function createGrokBuildAdapter(session) {
1120
1176
  };
1121
1177
  }
1122
1178
 
1179
+ // src/session.js
1180
+ function resolveGrokBin(env = process.env, exists = existsSync2) {
1181
+ if (typeof env.DSH_GROK_BIN === "string" && env.DSH_GROK_BIN.trim()) return env.DSH_GROK_BIN.trim();
1182
+ const local = join2(grokHome(env), "bin", "grok");
1183
+ if (exists(local)) return local;
1184
+ return "grok";
1185
+ }
1186
+ function grokCliAvailable(env = process.env, exists = existsSync2) {
1187
+ const bin = resolveGrokBin(env, exists);
1188
+ if (bin.includes("/") || bin.includes("\\")) return exists(bin);
1189
+ const delimiter = process.platform === "win32" ? ";" : ":";
1190
+ return String(env.PATH ?? "").split(delimiter).some((dir) => dir && exists(join2(dir, bin)));
1191
+ }
1192
+ function spawnGrokLogin(options = {}) {
1193
+ const spawnFn = options.spawn ?? spawn;
1194
+ const bin = resolveGrokBin(options.env, options.exists);
1195
+ const args = options.device ? ["login", "--device-auth"] : ["login"];
1196
+ return new Promise((resolve, reject) => {
1197
+ const child = spawnFn(bin, args, {
1198
+ stdio: options.stdio ?? "inherit",
1199
+ env: options.env ?? process.env
1200
+ });
1201
+ child.on("error", (error) => {
1202
+ reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
1203
+ });
1204
+ child.on("exit", (code, signal) => {
1205
+ if (code === 0) resolve({ ok: true, code });
1206
+ else reject(new Error(signal ? `grok login terminated by ${signal}` : `grok login exited with code ${code ?? "unknown"}`));
1207
+ });
1208
+ });
1209
+ }
1210
+ async function defaultCredentialRefOf() {
1211
+ try {
1212
+ const mod = await optionalImport("@deepseek-ai/dsh-credentials");
1213
+ if (typeof mod?.credentialRef === "function") return mod.credentialRef(CREDENTIAL_REF_NAME);
1214
+ } catch {
1215
+ }
1216
+ return CREDENTIAL_REF_NAME;
1217
+ }
1218
+ function scheduleDeferred(run) {
1219
+ if (typeof setImmediate === "function") setImmediate(run);
1220
+ else queueMicrotask(run);
1221
+ }
1222
+ function withTimeout(promise, ms, message, options = {}) {
1223
+ let timer;
1224
+ const shouldUnref = options.unref !== false;
1225
+ return Promise.race([
1226
+ promise,
1227
+ new Promise((_, reject) => {
1228
+ timer = setTimeout(() => reject(new Error(message)), ms);
1229
+ if (shouldUnref && typeof timer?.unref === "function") timer.unref();
1230
+ })
1231
+ ]).finally(() => {
1232
+ if (timer) clearTimeout(timer);
1233
+ });
1234
+ }
1235
+ function createSessionService({
1236
+ credentials,
1237
+ logger,
1238
+ onCatalogChange,
1239
+ fetchBillingUsage: fetchBilling = fetchBillingUsage,
1240
+ loadCatalog: loadCatalogFn = loadCatalog,
1241
+ readAuth = readGrokAuthSession,
1242
+ storeTokenTimeoutMs = STORE_TOKEN_TIMEOUT_MS,
1243
+ credentialsIoTimeoutMs = CREDENTIALS_IO_TIMEOUT_MS,
1244
+ credentialRefOf = defaultCredentialRefOf
1245
+ } = {}) {
1246
+ let catalog = { models: [], source: "signed-out", error: void 0 };
1247
+ let lastPublic = publicSessionView(void 0);
1248
+ let lastUsage = unavailable("Not fetched yet");
1249
+ let memoryAccessToken;
1250
+ let memorySessionTouched = false;
1251
+ const resolveTimeoutMs = () => {
1252
+ return typeof storeTokenTimeoutMs === "number" && storeTokenTimeoutMs > 0 ? storeTokenTimeoutMs : STORE_TOKEN_TIMEOUT_MS;
1253
+ };
1254
+ const resolveIoTimeoutMs = () => {
1255
+ return typeof credentialsIoTimeoutMs === "number" && credentialsIoTimeoutMs > 0 ? credentialsIoTimeoutMs : CREDENTIALS_IO_TIMEOUT_MS;
1256
+ };
1257
+ const notifyCatalogChange = () => {
1258
+ scheduleDeferred(() => {
1259
+ try {
1260
+ onCatalogChange?.();
1261
+ } catch (error) {
1262
+ logger?.warn?.(
1263
+ "Grok subscription catalog change notify failed: %s",
1264
+ error instanceof Error ? error.message : "unknown"
1265
+ );
1266
+ }
1267
+ });
1268
+ };
1269
+ const readStoredToken = async () => {
1270
+ if (typeof memoryAccessToken === "string" && memoryAccessToken.length > 0) {
1271
+ return memoryAccessToken;
1272
+ }
1273
+ if (memorySessionTouched) return void 0;
1274
+ if (!credentials?.resolve) return void 0;
1275
+ const timeoutMs = resolveIoTimeoutMs();
1276
+ try {
1277
+ const hit = await withTimeout(
1278
+ (async () => {
1279
+ const ref = await credentialRefOf();
1280
+ return credentials.resolve(ref);
1281
+ })(),
1282
+ timeoutMs,
1283
+ `Resolving Grok credential timed out after ${timeoutMs}ms`
1284
+ );
1285
+ const value = hit?.value;
1286
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1287
+ } catch (error) {
1288
+ logger?.warn?.(
1289
+ "Grok subscription credential resolve failed: %s",
1290
+ error instanceof Error ? error.message : "unknown"
1291
+ );
1292
+ return void 0;
1293
+ }
1294
+ };
1295
+ const persistToken = async (token) => {
1296
+ if (!credentials?.set) throw new Error("DSH credentials service is unavailable");
1297
+ const timeoutMs = resolveTimeoutMs();
1298
+ await withTimeout(
1299
+ (async () => {
1300
+ const ref = await credentialRefOf();
1301
+ await credentials.set(ref, token);
1302
+ })(),
1303
+ timeoutMs,
1304
+ `Storing Grok credential timed out after ${timeoutMs}ms`
1305
+ );
1306
+ };
1307
+ const clearToken = async () => {
1308
+ if (!credentials?.unset) return;
1309
+ const timeoutMs = resolveTimeoutMs();
1310
+ await withTimeout(
1311
+ (async () => {
1312
+ const ref = await credentialRefOf();
1313
+ await credentials.unset(ref);
1314
+ })(),
1315
+ timeoutMs,
1316
+ `Clearing Grok credential timed out after ${timeoutMs}ms`
1317
+ );
1318
+ };
1319
+ const refreshUsage = async () => {
1320
+ const token = await readStoredToken();
1321
+ if (!token) {
1322
+ lastUsage = unavailable("Not signed in");
1323
+ return lastUsage;
1324
+ }
1325
+ try {
1326
+ lastUsage = await fetchBilling(token);
1327
+ } catch (error) {
1328
+ logger?.warn?.(
1329
+ "Grok subscription usage fetch failed: %s",
1330
+ error instanceof Error ? error.message : "unknown"
1331
+ );
1332
+ lastUsage = unavailable("Usage fetch failed");
1333
+ }
1334
+ return lastUsage;
1335
+ };
1336
+ const refreshCatalog = async () => {
1337
+ const token = await readStoredToken();
1338
+ if (!token) {
1339
+ catalog = { models: [], source: "signed-out", error: void 0 };
1340
+ notifyCatalogChange();
1341
+ return catalog;
1342
+ }
1343
+ catalog = await loadCatalogFn(token);
1344
+ notifyCatalogChange();
1345
+ return catalog;
1346
+ };
1347
+ const kickBackgroundUsageRefresh = () => {
1348
+ void refreshUsage().catch((error) => {
1349
+ logger?.warn?.(
1350
+ "Grok subscription background usage refresh failed: %s",
1351
+ error instanceof Error ? error.message : "unknown"
1352
+ );
1353
+ });
1354
+ };
1355
+ const kickBackgroundCatalogRefresh = () => {
1356
+ void refreshCatalog().catch((error) => {
1357
+ logger?.warn?.(
1358
+ "Grok subscription background catalog refresh failed: %s",
1359
+ error instanceof Error ? error.message : "unknown"
1360
+ );
1361
+ });
1362
+ };
1363
+ const pull = async () => {
1364
+ const parsed = readAuth();
1365
+ if (!parsed.session) {
1366
+ memoryAccessToken = void 0;
1367
+ memorySessionTouched = true;
1368
+ catalog = { models: [], source: "signed-out", error: void 0 };
1369
+ lastPublic = publicSessionView(void 0);
1370
+ lastUsage = unavailable("Not signed in");
1371
+ scheduleDeferred(() => {
1372
+ void clearToken().catch((error) => {
1373
+ logger?.warn?.(
1374
+ "Grok subscription deferred credential clear failed: %s",
1375
+ error instanceof Error ? error.message : "unknown"
1376
+ );
1377
+ });
1378
+ });
1379
+ const message = parsed.reason === "api-key-only" ? "Found an API-key entry only. Sign in with SuperGrok / X Premium via grok login." : "No Grok Build subscription session in auth.json. Run grok login first.";
1380
+ notifyCatalogChange();
1381
+ return { ok: false, error: message, account: lastPublic, catalog, usage: lastUsage };
1382
+ }
1383
+ const accessToken = parsed.session.accessToken;
1384
+ memoryAccessToken = accessToken;
1385
+ memorySessionTouched = true;
1386
+ lastPublic = publicSessionView(parsed.session);
1387
+ scheduleDeferred(() => {
1388
+ void persistToken(accessToken).catch((error) => {
1389
+ logger?.warn?.(
1390
+ "Grok subscription deferred credential persist failed: %s",
1391
+ error instanceof Error ? error.message : "unknown"
1392
+ );
1393
+ });
1394
+ });
1395
+ notifyCatalogChange();
1396
+ kickBackgroundCatalogRefresh();
1397
+ kickBackgroundUsageRefresh();
1398
+ return { ok: true, account: lastPublic, catalog, usage: lastUsage };
1399
+ };
1400
+ const status = async () => {
1401
+ if (typeof memoryAccessToken === "string" && memoryAccessToken.length > 0 || lastPublic.signedIn === true) {
1402
+ return { account: lastPublic, catalog, usage: lastUsage, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
1403
+ }
1404
+ try {
1405
+ const parsed = readAuth();
1406
+ if (parsed.session) {
1407
+ memoryAccessToken = parsed.session.accessToken;
1408
+ memorySessionTouched = true;
1409
+ lastPublic = publicSessionView(parsed.session);
1410
+ return { account: lastPublic, catalog, usage: lastUsage, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
1411
+ }
1412
+ } catch (error) {
1413
+ logger?.warn?.(
1414
+ "Grok subscription auth.json read failed during status: %s",
1415
+ error instanceof Error ? error.message : "unknown"
1416
+ );
1417
+ }
1418
+ let token;
1419
+ try {
1420
+ token = await readStoredToken();
1421
+ } catch (error) {
1422
+ logger?.warn?.("could not resolve Grok subscription credential: %s", error instanceof Error ? error.message : "unknown");
1423
+ token = void 0;
1424
+ }
1425
+ if (!token) {
1426
+ lastPublic = publicSessionView(void 0);
1427
+ catalog = { models: [], source: "signed-out", error: void 0 };
1428
+ lastUsage = unavailable("Not signed in");
1429
+ return { account: lastPublic, catalog, usage: lastUsage, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
1430
+ }
1431
+ memoryAccessToken = token;
1432
+ lastPublic = Object.freeze({ signedIn: true, maskedAccount: "\u2022\u2022\u2022\u2022", authMode: "oidc", source: "dsh-credentials" });
1433
+ return { account: lastPublic, catalog, usage: lastUsage, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
1434
+ };
1435
+ const login = async (options) => {
1436
+ await spawnGrokLogin(options);
1437
+ return pull();
1438
+ };
1439
+ const logout = async () => {
1440
+ memoryAccessToken = void 0;
1441
+ memorySessionTouched = true;
1442
+ await clearToken().catch((error) => {
1443
+ logger?.warn?.(
1444
+ "Grok subscription credential clear failed: %s",
1445
+ error instanceof Error ? error.message : "unknown"
1446
+ );
1447
+ });
1448
+ catalog = { models: [], source: "signed-out", error: void 0 };
1449
+ lastPublic = publicSessionView(void 0);
1450
+ lastUsage = unavailable("Not signed in");
1451
+ notifyCatalogChange();
1452
+ return { ok: true, account: lastPublic, catalog, usage: lastUsage };
1453
+ };
1454
+ return {
1455
+ pull,
1456
+ status,
1457
+ refreshCatalog,
1458
+ refreshUsage,
1459
+ login,
1460
+ logout,
1461
+ currentToken: readStoredToken,
1462
+ models: () => catalog.models,
1463
+ catalog: () => catalog,
1464
+ usage: () => lastUsage,
1465
+ publicAccount: () => lastPublic
1466
+ };
1467
+ }
1468
+
1469
+ // src/rpc-contract.js
1470
+ var RPC_ENDPOINTS = Object.freeze([
1471
+ "status",
1472
+ "login/cli",
1473
+ "login/device",
1474
+ "pull",
1475
+ "logout",
1476
+ "catalog/refresh",
1477
+ "usage",
1478
+ "usage/refresh"
1479
+ ]);
1480
+ function publicResult(value) {
1481
+ return { ok: true, value };
1482
+ }
1483
+ function publicError(error, fallback = "Grok subscription request failed") {
1484
+ const message = error instanceof Error ? error.message : fallback;
1485
+ return { ok: false, error: { code: "internal", message, details: { issues: [] } } };
1486
+ }
1487
+
1488
+ // src/rpc.js
1489
+ function stripSecrets(value) {
1490
+ if (!value || typeof value !== "object") return value;
1491
+ const next = { ...value };
1492
+ delete next.accessToken;
1493
+ delete next.token;
1494
+ delete next.refresh;
1495
+ delete next.refresh_token;
1496
+ delete next.key;
1497
+ if (next.catalog) {
1498
+ next.catalog = {
1499
+ source: next.catalog.source,
1500
+ error: next.catalog.error,
1501
+ models: Array.isArray(next.catalog.models) ? next.catalog.models.map((model) => ({
1502
+ id: model.id,
1503
+ name: model.name,
1504
+ contextWindow: model.contextWindow,
1505
+ reasoning: model.reasoning,
1506
+ source: model.source
1507
+ })) : []
1508
+ };
1509
+ }
1510
+ if (next.account) {
1511
+ next.account = {
1512
+ signedIn: next.account.signedIn === true,
1513
+ maskedAccount: next.account.maskedAccount,
1514
+ authMode: next.account.authMode,
1515
+ expiresAt: next.account.expiresAt,
1516
+ source: next.account.source
1517
+ };
1518
+ }
1519
+ if (next.usage) {
1520
+ next.usage = sanitizeUsage(next.usage);
1521
+ }
1522
+ return next;
1523
+ }
1524
+ function sanitizeUsage(usage) {
1525
+ if (!usage || typeof usage !== "object") {
1526
+ return { status: "unavailable", reason: "Usage unavailable", experimental: true, source: "billing-credits-undocumented" };
1527
+ }
1528
+ const out = {
1529
+ status: usage.status === "ok" ? "ok" : "unavailable",
1530
+ experimental: true,
1531
+ source: typeof usage.source === "string" ? usage.source : "billing-credits-undocumented"
1532
+ };
1533
+ if (out.status !== "ok") {
1534
+ out.reason = typeof usage.reason === "string" ? usage.reason : "Usage unavailable";
1535
+ return out;
1536
+ }
1537
+ if (typeof usage.usedPercent === "number" && Number.isFinite(usage.usedPercent)) out.usedPercent = usage.usedPercent;
1538
+ if (typeof usage.remainingPercent === "number" && Number.isFinite(usage.remainingPercent)) out.remainingPercent = usage.remainingPercent;
1539
+ if (typeof usage.periodStart === "string") out.periodStart = usage.periodStart;
1540
+ if (typeof usage.periodStartLocal === "string") out.periodStartLocal = usage.periodStartLocal;
1541
+ if (typeof usage.periodEnd === "string") out.periodEnd = usage.periodEnd;
1542
+ if (typeof usage.periodEndLocal === "string") out.periodEndLocal = usage.periodEndLocal;
1543
+ if (typeof usage.fetchedAt === "string") out.fetchedAt = usage.fetchedAt;
1544
+ if (Array.isArray(usage.productUsage)) {
1545
+ out.productUsage = usage.productUsage.filter((row) => row && typeof row === "object").map((row) => ({
1546
+ ...typeof row.name === "string" ? { name: row.name } : {},
1547
+ ...typeof row.usedPercent === "number" && Number.isFinite(row.usedPercent) ? { usedPercent: row.usedPercent } : {}
1548
+ }));
1549
+ }
1550
+ return out;
1551
+ }
1552
+ async function dispatch(session, endpoint) {
1553
+ if (endpoint === "status") return publicResult(stripSecrets(await session.status()));
1554
+ if (endpoint === "pull") return publicResult(stripSecrets(await session.pull()));
1555
+ if (endpoint === "logout") return publicResult(stripSecrets(await session.logout()));
1556
+ if (endpoint === "catalog/refresh") {
1557
+ const catalog = await session.refreshCatalog();
1558
+ return publicResult(stripSecrets({ catalog, account: session.publicAccount(), usage: session.usage?.() }));
1559
+ }
1560
+ if (endpoint === "usage") {
1561
+ return publicResult(stripSecrets({ usage: session.usage?.() ?? { status: "unavailable", reason: "Usage unavailable", experimental: true } }));
1562
+ }
1563
+ if (endpoint === "usage/refresh") {
1564
+ const usage = await session.refreshUsage();
1565
+ return publicResult(stripSecrets({ usage, account: session.publicAccount() }));
1566
+ }
1567
+ if (endpoint === "login/cli") return publicResult(stripSecrets(await session.login({ device: false })));
1568
+ if (endpoint === "login/device") return publicResult(stripSecrets(await session.login({ device: true })));
1569
+ return publicError(new Error(`Unknown Grok subscription RPC: ${endpoint}`));
1570
+ }
1571
+ function createRpcHandler(session, options = {}) {
1572
+ const timeoutMs = typeof options.timeoutMs === "number" && options.timeoutMs > 0 ? options.timeoutMs : RPC_HANDLER_TIMEOUT_MS;
1573
+ return async function handle(endpoint, _payload, _signal) {
1574
+ try {
1575
+ return await withTimeout(
1576
+ dispatch(session, endpoint),
1577
+ timeoutMs,
1578
+ `Grok subscription RPC "${endpoint}" timed out after ${timeoutMs}ms`,
1579
+ { unref: false }
1580
+ );
1581
+ } catch (error) {
1582
+ return publicError(error);
1583
+ }
1584
+ };
1585
+ }
1586
+
1587
+ // src/transport.js
1588
+ function parseEnvelope(body, method) {
1589
+ if (!body || typeof body !== "object") return void 0;
1590
+ if (body.method !== method) return void 0;
1591
+ return {
1592
+ rpcId: body.rpcId,
1593
+ payload: body.payload
1594
+ };
1595
+ }
1596
+ var cachedClientRequestSchema;
1597
+ var schemaLoadStarted = false;
1598
+ function kickSchemaLoad() {
1599
+ if (schemaLoadStarted) return;
1600
+ schemaLoadStarted = true;
1601
+ void optionalImport("@deepseek-ai/dsh-client-connection").then((mod) => {
1602
+ if (mod?.clientRequestSchema && typeof mod.clientRequestSchema.safeParse === "function") {
1603
+ cachedClientRequestSchema = mod.clientRequestSchema;
1604
+ }
1605
+ }).catch(() => {
1606
+ });
1607
+ }
1608
+ function resolveEnvelope(body, method) {
1609
+ const schema = cachedClientRequestSchema;
1610
+ if (schema) {
1611
+ const envelope = schema.safeParse(body);
1612
+ if (!envelope.success || envelope.data.method !== method) return void 0;
1613
+ return { rpcId: envelope.data.rpcId, payload: envelope.data.payload };
1614
+ }
1615
+ return parseEnvelope(body, method);
1616
+ }
1617
+ function registerSubscriptionTransport(connection, handler) {
1618
+ kickSchemaLoad();
1619
+ const disposers = [];
1620
+ try {
1621
+ for (const endpoint of RPC_ENDPOINTS) {
1622
+ const method = `grok-subscription/${endpoint}`;
1623
+ if (typeof connection?.fetch?.register !== "function") {
1624
+ throw new Error("DSH connection.fetch.register is unavailable");
1625
+ }
1626
+ disposers.push(connection.fetch.register({
1627
+ path: `/api/${method}`,
1628
+ methods: ["POST"],
1629
+ requestBody: "buffered",
1630
+ async fetch(request) {
1631
+ if (request.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") {
1632
+ return new Response("content type must be application/json", { status: 415 });
1633
+ }
1634
+ let body;
1635
+ try {
1636
+ body = await request.json();
1637
+ } catch {
1638
+ return new Response("invalid JSON", { status: 400 });
1639
+ }
1640
+ if (!cachedClientRequestSchema) {
1641
+ try {
1642
+ const mod = await optionalImport("@deepseek-ai/dsh-client-connection");
1643
+ if (mod?.clientRequestSchema && typeof mod.clientRequestSchema.safeParse === "function") {
1644
+ cachedClientRequestSchema = mod.clientRequestSchema;
1645
+ }
1646
+ } catch {
1647
+ }
1648
+ }
1649
+ const envelope = resolveEnvelope(body, method);
1650
+ if (!envelope) return new Response("invalid RPC envelope", { status: 400 });
1651
+ let result;
1652
+ try {
1653
+ request.signal?.throwIfAborted?.();
1654
+ result = await handler(endpoint, envelope.payload, request.signal);
1655
+ } catch {
1656
+ result = { ok: false, error: { code: "internal", message: "Grok subscription request failed", details: { issues: [] } } };
1657
+ }
1658
+ return Response.json({ type: "server-response", rpcId: envelope.rpcId, result });
1659
+ }
1660
+ }));
1661
+ }
1662
+ } catch (error) {
1663
+ for (const dispose of disposers.reverse()) dispose?.();
1664
+ throw error;
1665
+ }
1666
+ return () => {
1667
+ for (const dispose of disposers.reverse()) dispose?.();
1668
+ };
1669
+ }
1670
+
1123
1671
  // src/index.js
1124
1672
  var name = CORDIS_ID;
1125
- var inject = ["llm", "credentials", "settings", "web"];
1126
- function apply(ctx) {
1673
+ var inject = ["llm", "web"];
1674
+ function scheduleDeferred2(run) {
1675
+ if (typeof setImmediate === "function") setImmediate(run);
1676
+ else queueMicrotask(run);
1677
+ }
1678
+ function softService(ctx, key) {
1679
+ try {
1680
+ if (typeof ctx.get === "function") return ctx.get(key) ?? void 0;
1681
+ } catch {
1682
+ }
1683
+ return void 0;
1684
+ }
1685
+ function apply(ctx, options = {}) {
1127
1686
  let active = true;
1128
1687
  if (typeof ctx.effect === "function") {
1129
1688
  ctx.effect(
@@ -1134,60 +1693,103 @@ function apply(ctx) {
1134
1693
  );
1135
1694
  }
1136
1695
  const notifyCatalogChange = () => {
1137
- if (!active) return;
1138
- try {
1139
- ctx.emit("llm/adapters-updated");
1140
- } catch (error) {
1141
- ctx.logger?.warn?.("an llm/adapters-updated listener failed");
1142
- ctx.logger?.warn?.(error);
1143
- }
1696
+ scheduleDeferred2(() => {
1697
+ if (!active) return;
1698
+ try {
1699
+ ctx.emit("llm/adapters-updated");
1700
+ } catch (error) {
1701
+ ctx.logger?.warn?.("an llm/adapters-updated listener failed");
1702
+ ctx.logger?.warn?.(error);
1703
+ }
1704
+ });
1144
1705
  };
1706
+ const credentials = softService(ctx, "credentials");
1145
1707
  const session = createSessionService({
1146
- credentials: ctx.credentials,
1708
+ credentials,
1147
1709
  logger: ctx.logger,
1148
1710
  onCatalogChange: notifyCatalogChange
1149
1711
  });
1150
1712
  const handler = createRpcHandler(session);
1151
- void boot(ctx, session, notifyCatalogChange);
1713
+ const createSync = options.createSync ?? createGrokBuildAdapterSync;
1714
+ try {
1715
+ const created = createSync(session);
1716
+ if (created.note) ctx.logger?.debug?.(created.note);
1717
+ ctx.llm.registerAdapter([PROVIDER_ID], created.adapter);
1718
+ notifyCatalogChange();
1719
+ } catch (error) {
1720
+ ctx.logger?.warn?.(
1721
+ "Grok subscription duck adapter failed to register: %s",
1722
+ error instanceof Error ? error.message : "unknown"
1723
+ );
1724
+ }
1725
+ const defer = options.defer ?? scheduleDeferred2;
1726
+ defer(() => {
1727
+ void deferredBoot(ctx, session, notifyCatalogChange, options);
1728
+ });
1152
1729
  ctx.inject(["connection"], (connectionContext) => connectionContext.effect(
1153
1730
  () => registerSubscriptionTransport(connectionContext.connection, handler),
1154
1731
  "grok-subscription: host-only account RPC"
1155
1732
  ));
1156
1733
  }
1157
- async function boot(ctx, session, notifyCatalogChange) {
1158
- await tryRegisterSettings(ctx);
1734
+ async function deferredBoot(ctx, session, notifyCatalogChange, options = {}) {
1735
+ if (!options.skipSettings) {
1736
+ await tryRegisterSettings(ctx);
1737
+ }
1159
1738
  try {
1160
- await session.pull();
1739
+ ctx.llm.registerConfigurableProviders?.([{
1740
+ provider: PROVIDER_ID,
1741
+ displayName: DISPLAY_NAME,
1742
+ settingsNs: SETTINGS_NAMESPACE,
1743
+ settingsPath: []
1744
+ }]);
1161
1745
  } catch (error) {
1162
- ctx.logger?.debug?.("Grok subscription startup pull skipped: %s", error instanceof Error ? error.message : "unknown");
1746
+ ctx.logger?.debug?.(
1747
+ "Grok subscription provider directory skipped: %s",
1748
+ error instanceof Error ? error.message : "unknown"
1749
+ );
1163
1750
  }
1751
+ if (!options.skipPull) {
1752
+ void session.pull().catch((error) => {
1753
+ ctx.logger?.debug?.(
1754
+ "Grok subscription startup pull skipped: %s",
1755
+ error instanceof Error ? error.message : "unknown"
1756
+ );
1757
+ });
1758
+ }
1759
+ if (options.skipUpgrade) return;
1760
+ const createAsync = options.createAsync ?? createGrokBuildAdapter;
1164
1761
  try {
1165
- const created = await createGrokBuildAdapter(session);
1166
- if (created.note) ctx.logger?.warn?.(created.note);
1167
- ctx.llm.registerAdapter([PROVIDER_ID], created.adapter);
1168
- try {
1169
- ctx.llm.registerConfigurableProviders?.([{
1170
- provider: PROVIDER_ID,
1171
- displayName: DISPLAY_NAME,
1172
- settingsNs: SETTINGS_NAMESPACE,
1173
- settingsPath: []
1174
- }]);
1175
- } catch (error) {
1176
- ctx.logger?.debug?.("Grok subscription provider directory skipped: %s", error instanceof Error ? error.message : "unknown");
1762
+ const created = await createAsync(session);
1763
+ if (created.kind === "pi-ai") {
1764
+ if (created.note) ctx.logger?.warn?.(created.note);
1765
+ ctx.llm.registerAdapter([PROVIDER_ID], created.adapter);
1766
+ notifyCatalogChange?.();
1767
+ } else if (created.note) {
1768
+ ctx.logger?.debug?.(created.note);
1177
1769
  }
1178
- notifyCatalogChange?.();
1179
1770
  } catch (error) {
1180
- ctx.logger?.warn?.("Grok subscription adapter failed to start: %s", error instanceof Error ? error.message : "unknown");
1771
+ ctx.logger?.warn?.(
1772
+ "Grok subscription adapter upgrade failed: %s",
1773
+ error instanceof Error ? error.message : "unknown"
1774
+ );
1181
1775
  }
1182
1776
  }
1183
1777
  async function tryRegisterSettings(ctx) {
1184
- if (typeof ctx.settings?.register !== "function") return;
1778
+ const settings = softService(ctx, "settings");
1779
+ if (typeof settings?.register !== "function") return;
1185
1780
  try {
1186
- const mod = await import("@deepseek-ai/schemastery");
1781
+ const mod = await optionalImport("@deepseek-ai/schemastery");
1782
+ if (!mod) {
1783
+ ctx.logger?.debug?.("Grok subscription settings namespace skipped: schemastery unavailable or timed out");
1784
+ return;
1785
+ }
1187
1786
  const z = mod.default ?? mod;
1188
- ctx.settings.register(SETTINGS_NAMESPACE, z.object({}));
1787
+ settings.register(SETTINGS_NAMESPACE, z.object({}));
1189
1788
  } catch (error) {
1190
- ctx.logger?.debug?.("Grok subscription settings namespace skipped: %s", error instanceof Error ? error.message : "unknown");
1789
+ ctx.logger?.debug?.(
1790
+ "Grok subscription settings namespace skipped: %s",
1791
+ error instanceof Error ? error.message : "unknown"
1792
+ );
1191
1793
  }
1192
1794
  }
1193
1795
  export {
@@ -1195,6 +1797,9 @@ export {
1195
1797
  DISPLAY_NAME_ZH,
1196
1798
  PROVIDER_ID,
1197
1799
  apply,
1800
+ deferredBoot,
1198
1801
  inject,
1199
- name
1802
+ name,
1803
+ scheduleDeferred2 as scheduleDeferred,
1804
+ softService
1200
1805
  };