perplexity-user-mcp 0.8.36

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 (125) hide show
  1. package/README.md +192 -0
  2. package/dist/attachments.d.ts +20 -0
  3. package/dist/attachments.mjs +43 -0
  4. package/dist/checks/browser.d.ts +100 -0
  5. package/dist/checks/browser.mjs +89 -0
  6. package/dist/checks/config.d.ts +91 -0
  7. package/dist/checks/config.mjs +88 -0
  8. package/dist/checks/ide.d.ts +89 -0
  9. package/dist/checks/ide.mjs +80 -0
  10. package/dist/checks/mcp.d.ts +61 -0
  11. package/dist/checks/mcp.mjs +56 -0
  12. package/dist/checks/native-deps.d.ts +131 -0
  13. package/dist/checks/native-deps.mjs +115 -0
  14. package/dist/checks/network.d.ts +71 -0
  15. package/dist/checks/network.mjs +70 -0
  16. package/dist/checks/probe.d.ts +93 -0
  17. package/dist/checks/probe.mjs +82 -0
  18. package/dist/checks/profiles.d.ts +99 -0
  19. package/dist/checks/profiles.mjs +90 -0
  20. package/dist/checks/runtime.d.ts +89 -0
  21. package/dist/checks/runtime.mjs +90 -0
  22. package/dist/checks/vault.d.ts +101 -0
  23. package/dist/checks/vault.mjs +90 -0
  24. package/dist/chunk-3B276PGG.mjs +115 -0
  25. package/dist/chunk-4UEJOM6W.mjs +9 -0
  26. package/dist/chunk-6EP2BLTV.mjs +205 -0
  27. package/dist/chunk-6YMQVLFX.mjs +146 -0
  28. package/dist/chunk-7JL36EBH.mjs +118 -0
  29. package/dist/chunk-DPGMKSSA.mjs +57 -0
  30. package/dist/chunk-H4BUAPPO.mjs +1950 -0
  31. package/dist/chunk-HMKLWVXB.mjs +109 -0
  32. package/dist/chunk-HTUAQRKH.mjs +125 -0
  33. package/dist/chunk-HU5B4FXS.mjs +139 -0
  34. package/dist/chunk-KCXM2M4B.mjs +1006 -0
  35. package/dist/chunk-LKJMLGFP.mjs +237 -0
  36. package/dist/chunk-LZPLNZ5U.mjs +67 -0
  37. package/dist/chunk-MTDFKNXX.mjs +19 -0
  38. package/dist/chunk-OF4DMAPJ.mjs +511 -0
  39. package/dist/chunk-PE23RMXY.mjs +43 -0
  40. package/dist/chunk-Q2VY4R5F.mjs +175 -0
  41. package/dist/chunk-S5VD7WTU.mjs +2540 -0
  42. package/dist/chunk-SVPRB62V.mjs +106 -0
  43. package/dist/chunk-TQLCLE4L.mjs +345 -0
  44. package/dist/chunk-U3DGFLXZ.mjs +43 -0
  45. package/dist/chunk-X45O6YD3.mjs +688 -0
  46. package/dist/chunk-XKSWCEGI.mjs +168 -0
  47. package/dist/chunk-Z7DAACGZ.mjs +534 -0
  48. package/dist/chunk-ZQFUZPLO.mjs +257 -0
  49. package/dist/cli.d.ts +952 -0
  50. package/dist/cli.mjs +827 -0
  51. package/dist/client.d.ts +355 -0
  52. package/dist/client.mjs +27 -0
  53. package/dist/cloud-sync.d-Cqt6y18U.d.ts +42 -0
  54. package/dist/cloud-sync.d.ts +42 -0
  55. package/dist/cloud-sync.mjs +17 -0
  56. package/dist/config.d.ts +186 -0
  57. package/dist/config.mjs +54 -0
  58. package/dist/daemon/attach.d.ts +36 -0
  59. package/dist/daemon/attach.mjs +25 -0
  60. package/dist/daemon/audit.d.ts +23 -0
  61. package/dist/daemon/audit.mjs +12 -0
  62. package/dist/daemon/client-http.d.ts +42 -0
  63. package/dist/daemon/client-http.mjs +29 -0
  64. package/dist/daemon/index.d.ts +14 -0
  65. package/dist/daemon/index.mjs +110 -0
  66. package/dist/daemon/install-tunnel.d.ts +46 -0
  67. package/dist/daemon/install-tunnel.mjs +14 -0
  68. package/dist/daemon/launcher.d.ts +163 -0
  69. package/dist/daemon/launcher.mjs +50 -0
  70. package/dist/daemon/lockfile.d.ts +29 -0
  71. package/dist/daemon/lockfile.mjs +18 -0
  72. package/dist/daemon/server.d.ts +159 -0
  73. package/dist/daemon/server.mjs +20 -0
  74. package/dist/daemon/token.d.ts +17 -0
  75. package/dist/daemon/token.mjs +17 -0
  76. package/dist/daemon/tunnel-providers/index.d.ts +330 -0
  77. package/dist/daemon/tunnel-providers/index.mjs +57 -0
  78. package/dist/daemon/tunnel.d.ts +23 -0
  79. package/dist/daemon/tunnel.mjs +9 -0
  80. package/dist/doctor-report.d.ts +24 -0
  81. package/dist/doctor-report.mjs +14 -0
  82. package/dist/doctor.d-CXmUqOXX.d.ts +43 -0
  83. package/dist/doctor.d.ts +44 -0
  84. package/dist/doctor.mjs +16 -0
  85. package/dist/export.d.ts +19 -0
  86. package/dist/export.mjs +15 -0
  87. package/dist/health-check.d.ts +108 -0
  88. package/dist/health-check.mjs +92 -0
  89. package/dist/history-store.d-BzjBF2m3.d.ts +65 -0
  90. package/dist/history-store.d.ts +65 -0
  91. package/dist/history-store.mjs +48 -0
  92. package/dist/impit-login-runner.d.ts +469 -0
  93. package/dist/impit-login-runner.mjs +685 -0
  94. package/dist/index.d.ts +159 -0
  95. package/dist/index.mjs +236 -0
  96. package/dist/login-runner.d.ts +333 -0
  97. package/dist/login-runner.mjs +320 -0
  98. package/dist/logout.d.ts +28 -0
  99. package/dist/logout.mjs +45 -0
  100. package/dist/manual-login-runner.d.ts +150 -0
  101. package/dist/manual-login-runner.mjs +146 -0
  102. package/dist/native-deps-BNThFHxa.d.ts +175 -0
  103. package/dist/native-deps-YNKXITRY.mjs +139 -0
  104. package/dist/profiles.d-DqS1oZWr.d.ts +41 -0
  105. package/dist/profiles.d.ts +41 -0
  106. package/dist/profiles.mjs +33 -0
  107. package/dist/redact.d.ts +159 -0
  108. package/dist/redact.mjs +11 -0
  109. package/dist/refresh.d.ts +118 -0
  110. package/dist/refresh.mjs +21 -0
  111. package/dist/reinit-watcher.d.ts +15 -0
  112. package/dist/reinit-watcher.mjs +8 -0
  113. package/dist/session-metadata-B9aV_n5g.d.ts +148 -0
  114. package/dist/tty-prompt.d.ts +44 -0
  115. package/dist/tty-prompt.mjs +39 -0
  116. package/dist/vault.d-BtRSLZiM.d.ts +8 -0
  117. package/dist/vault.d.ts +37 -0
  118. package/dist/vault.mjs +21 -0
  119. package/dist/viewer-detect.d-HWGnyFAA.d.ts +4 -0
  120. package/dist/viewer-detect.d.ts +4 -0
  121. package/dist/viewer-detect.mjs +37 -0
  122. package/dist/viewers.d-BGCK6sw6.d.ts +10 -0
  123. package/dist/viewers.d.ts +18 -0
  124. package/dist/viewers.mjs +122 -0
  125. package/package.json +152 -0
@@ -0,0 +1,109 @@
1
+ // src/redact.js
2
+ var SECRET_PATTERNS = Object.freeze([
3
+ // OAuth / local prefixes come FIRST so a value like `"bearer":"pplx_at_…"`
4
+ // gets the specific oauth-access tag instead of the generic daemon-bearer
5
+ // one from the bearer-json catchall below.
6
+ { name: "oauth-access", kind: "oauth-access", re: /pplx_at_[A-Za-z0-9_\-]{10,}/g },
7
+ { name: "oauth-refresh", kind: "oauth-refresh", re: /pplx_rt_[A-Za-z0-9_\-]{10,}/g },
8
+ { name: "oauth-code", kind: "oauth-code", re: /pplx_ac_[A-Za-z0-9_\-]{10,}/g },
9
+ { name: "local-bearer", kind: "local-bearer", re: /pplx_local_[a-z0-9-]+_[A-Za-z0-9_\-]{10,}/g },
10
+ { name: "daemon-bearer-json", kind: "daemon-bearer", re: /"bearerToken"\s*:\s*"[A-Za-z0-9_\-]{30,}"/g },
11
+ // Matches the `"bearer":"..."` shape used by daemon:bearer:reveal:response
12
+ // so reveal-payload logs stay leak-free. Value is only required to be
13
+ // 20+ safe-identifier chars — covers both raw daemon bearers and future
14
+ // short-lived tokens.
15
+ { name: "bearer-json", kind: "daemon-bearer", re: /"bearer"\s*:\s*"[A-Za-z0-9_\-]{20,}"/g },
16
+ { name: "authorization-header", kind: "bearer-header", re: /[Aa]uthorization\s*:\s*Bearer\s+[A-Za-z0-9_\-\.]{20,}/g },
17
+ { name: "ngrok-authtoken", kind: "ngrok-authtoken", re: /"authtoken"\s*:\s*"\d+[A-Za-z0-9]{20,}"/g },
18
+ { name: "jwt", kind: "jwt", re: /eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+/g },
19
+ { name: "cf-clearance", kind: "cf-clearance", re: /cf_clearance=[^;\s]+/g },
20
+ { name: "perplexity-session", kind: "perplexity-session", re: /__Secure-next-auth\.session-token=[^;\s]+/g }
21
+ ]);
22
+ function redactSecrets(input) {
23
+ if (typeof input !== "string") return input;
24
+ let out = input;
25
+ for (const { re, kind } of SECRET_PATTERNS) {
26
+ out = out.replace(re, (match) => {
27
+ if (match.startsWith('"bearerToken"')) return `"bearerToken":"<redacted:${kind}>"`;
28
+ if (match.startsWith('"bearer"')) return `"bearer":"<redacted:${kind}>"`;
29
+ if (/^[Aa]uthorization\s*:/i.test(match)) return match.replace(/Bearer\s+[A-Za-z0-9_\-\.]{20,}/, `Bearer <redacted:${kind}>`);
30
+ if (match.startsWith('"authtoken"')) return `"authtoken":"<redacted:${kind}>"`;
31
+ if (match.startsWith("cf_clearance=")) return `cf_clearance=<redacted:${kind}>`;
32
+ if (match.startsWith("__Secure-next-auth.session-token=")) return `__Secure-next-auth.session-token=<redacted:${kind}>`;
33
+ return `<redacted:${kind}>`;
34
+ });
35
+ }
36
+ return out;
37
+ }
38
+ var PATTERNS = [
39
+ // Emails: RFC 5322 subset. Must come before generic token rules because
40
+ // emails contain characters that other rules would catch.
41
+ {
42
+ re: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
43
+ replace: "<email>"
44
+ },
45
+ // Perplexity user IDs: user_ followed by hex/alphanum (>=8 chars).
46
+ {
47
+ re: /\buser_[A-Fa-f0-9]{8,}\b/g,
48
+ replace: "<userId>"
49
+ },
50
+ // Home directory paths. Must replace before the "long opaque token" rule
51
+ // because long path segments would otherwise trip it.
52
+ {
53
+ re: /(\/Users\/|\/home\/)[^/\s]+/g,
54
+ replace: "<home>"
55
+ },
56
+ {
57
+ re: /([A-Z]:)\\Users\\[^\\]+/g,
58
+ replace: "<home>"
59
+ },
60
+ // IPv4
61
+ {
62
+ re: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g,
63
+ replace: "<ip>"
64
+ },
65
+ // IPv6. Three alternations cover: (1) full 8-group addresses, (2) :: compressed
66
+ // forms with at least one prefix group (e.g. 2001:db8::1, fe80::1), (3) leading-::
67
+ // forms like ::1 and ::. A function filter then requires hex letters (a-f/A-F) OR
68
+ // a double-colon so that pure-digit colon-separated strings like "23:59:59"
69
+ // (HH:MM:SS wall-clock) and ISO timestamps are left untouched.
70
+ {
71
+ re: /\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b|\b(?:[0-9a-fA-F]{1,4}:){1,7}:(?:[0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){0,6})?\b|(?<!\w)::(?:[0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){0,6})?(?!\w)/g,
72
+ replace: (match) => {
73
+ const hasHex = /[a-fA-F]/.test(match);
74
+ const hasDoubleColon = /::/.test(match);
75
+ return hasHex || hasDoubleColon ? "<ip>" : match;
76
+ }
77
+ },
78
+ // Long opaque tokens (base64 / hex, >=20 chars). Applied last so more
79
+ // specific rules win first. Captures key=value and replaces only the value.
80
+ {
81
+ re: /=([A-Za-z0-9+/=]{20,})/g,
82
+ replace: "=<redacted>"
83
+ }
84
+ ];
85
+ function redact(value, _seen) {
86
+ if (value == null) return value;
87
+ if (typeof value === "string") return redactString(value);
88
+ if (typeof value !== "object") return value;
89
+ const seen = _seen ?? /* @__PURE__ */ new WeakSet();
90
+ if (seen.has(value)) return "<circular>";
91
+ seen.add(value);
92
+ if (Array.isArray(value)) return value.map((v) => redact(v, seen));
93
+ const out = {};
94
+ for (const [k, v] of Object.entries(value)) out[k] = redact(v, seen);
95
+ return out;
96
+ }
97
+ function redactString(s) {
98
+ let out = redactSecrets(s);
99
+ for (const { re, replace } of PATTERNS) {
100
+ out = out.replace(re, replace);
101
+ }
102
+ return out;
103
+ }
104
+
105
+ export {
106
+ SECRET_PATTERNS,
107
+ redactSecrets,
108
+ redact
109
+ };
@@ -0,0 +1,125 @@
1
+ import {
2
+ safeAtomicWriteFileSync
3
+ } from "./chunk-MTDFKNXX.mjs";
4
+ import {
5
+ getConfigDir
6
+ } from "./chunk-XKSWCEGI.mjs";
7
+
8
+ // src/daemon/token.ts
9
+ import { randomBytes } from "crypto";
10
+ import { spawnSync } from "child_process";
11
+ import { chmodSync, existsSync, mkdirSync, readFileSync } from "fs";
12
+ import { dirname, join } from "path";
13
+ function getTokenPath(configDir = getConfigDir()) {
14
+ return join(configDir, "daemon.token");
15
+ }
16
+ function generateBearerToken() {
17
+ return randomBytes(32).toString("base64url");
18
+ }
19
+ function readToken(options = {}) {
20
+ const tokenPath = options.tokenPath ?? getTokenPath();
21
+ if (!existsSync(tokenPath)) {
22
+ return null;
23
+ }
24
+ const parsed = JSON.parse(readFileSync(tokenPath, "utf8"));
25
+ return normalizeRecord(parsed);
26
+ }
27
+ function ensureToken(options = {}) {
28
+ const existing = readToken(options);
29
+ if (existing) {
30
+ return existing;
31
+ }
32
+ const now = (options.now ?? defaultNow)();
33
+ const record = {
34
+ bearerToken: generateBearerToken(),
35
+ version: 1,
36
+ createdAt: now,
37
+ rotatedAt: now
38
+ };
39
+ writeToken(record, options);
40
+ return record;
41
+ }
42
+ function rotateToken(options = {}) {
43
+ const previous = readToken(options);
44
+ const now = (options.now ?? defaultNow)();
45
+ const record = {
46
+ bearerToken: generateBearerToken(),
47
+ version: (previous?.version ?? 0) + 1,
48
+ createdAt: previous?.createdAt ?? now,
49
+ rotatedAt: now
50
+ };
51
+ writeToken(record, options);
52
+ return record;
53
+ }
54
+ function writeToken(record, options = {}) {
55
+ const tokenPath = options.tokenPath ?? getTokenPath();
56
+ const normalized = normalizeRecord(record);
57
+ mkdirSync(dirname(tokenPath), { recursive: true });
58
+ safeAtomicWriteFileSync(tokenPath, JSON.stringify(normalized, null, 2) + "\n", { encoding: "utf8", mode: 384 });
59
+ applyPrivatePermissions(tokenPath);
60
+ }
61
+ function normalizeRecord(value) {
62
+ if (!value || typeof value !== "object") {
63
+ throw new Error("Daemon token file must contain a JSON object.");
64
+ }
65
+ const record = value;
66
+ if (typeof record.bearerToken !== "string" || record.bearerToken.length === 0) {
67
+ throw new Error("Daemon token file field 'bearerToken' must be a non-empty string.");
68
+ }
69
+ if (!Number.isInteger(record.version) || Number(record.version) <= 0) {
70
+ throw new Error("Daemon token file field 'version' must be a positive integer.");
71
+ }
72
+ if (typeof record.createdAt !== "string" || record.createdAt.length === 0) {
73
+ throw new Error("Daemon token file field 'createdAt' must be a non-empty string.");
74
+ }
75
+ if (typeof record.rotatedAt !== "string" || record.rotatedAt.length === 0) {
76
+ throw new Error("Daemon token file field 'rotatedAt' must be a non-empty string.");
77
+ }
78
+ return {
79
+ bearerToken: record.bearerToken,
80
+ version: Number(record.version),
81
+ createdAt: record.createdAt,
82
+ rotatedAt: record.rotatedAt
83
+ };
84
+ }
85
+ function applyPrivatePermissions(tokenPath) {
86
+ if (process.platform === "win32") {
87
+ restrictWindowsAcl(tokenPath);
88
+ return;
89
+ }
90
+ chmodSync(tokenPath, 384);
91
+ }
92
+ function restrictWindowsAcl(tokenPath) {
93
+ const username = getWindowsUserName();
94
+ const grantTarget = `${username}:(R,W)`;
95
+ const result = spawnSync("icacls", [tokenPath, "/inheritance:r", "/grant:r", grantTarget], {
96
+ encoding: "utf8",
97
+ windowsHide: true
98
+ });
99
+ if (result.status !== 0) {
100
+ const detail = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
101
+ throw new Error(`Failed to restrict daemon token ACL via icacls.${detail ? ` ${detail}` : ""}`);
102
+ }
103
+ }
104
+ function getWindowsUserName() {
105
+ const username = process.env.USERNAME;
106
+ const domain = process.env.USERDOMAIN;
107
+ if (domain && username) {
108
+ return `${domain}\\${username}`;
109
+ }
110
+ if (username) {
111
+ return username;
112
+ }
113
+ throw new Error("Unable to resolve Windows username for daemon token ACL.");
114
+ }
115
+ function defaultNow() {
116
+ return (/* @__PURE__ */ new Date()).toISOString();
117
+ }
118
+
119
+ export {
120
+ getTokenPath,
121
+ generateBearerToken,
122
+ readToken,
123
+ ensureToken,
124
+ rotateToken
125
+ };
@@ -0,0 +1,139 @@
1
+ // src/session-metadata.js
2
+ var API_VERSION_QUERY = "version=2.18&source=default";
3
+ function buildRuntimeEndpoints(origin) {
4
+ const base = origin.replace(/\/+$/, "");
5
+ return {
6
+ session: `${base}/api/auth/session?${API_VERSION_QUERY}`,
7
+ csrf: `${base}/api/auth/csrf?${API_VERSION_QUERY}`,
8
+ signInEmail: `${base}/api/auth/signin/email?${API_VERSION_QUERY}`,
9
+ otpRedirectLink: `${base}/api/auth/otp-redirect-link`,
10
+ ssoDetails: `${base}/rest/enterprise/organization/login/details?${API_VERSION_QUERY}`,
11
+ models: `${base}/rest/models/config?config_schema=v1&${API_VERSION_QUERY}`,
12
+ asi: `${base}/rest/billing/asi-access-decision?${API_VERSION_QUERY}`,
13
+ rateLimits: `${base}/rest/rate-limit/status?${API_VERSION_QUERY}`,
14
+ experiments: `${base}/rest/experiments/attributes?${API_VERSION_QUERY}`,
15
+ userInfo: `${base}/rest/user/info?${API_VERSION_QUERY}`
16
+ };
17
+ }
18
+ async function pageRequest(page, url, init = {}) {
19
+ return page.evaluate(async ({ url: target, init: requestInit }) => {
20
+ try {
21
+ const response = await fetch(target, {
22
+ credentials: "include",
23
+ ...requestInit
24
+ });
25
+ const contentType = response.headers.get("content-type") ?? "";
26
+ let json = null;
27
+ let text = null;
28
+ try {
29
+ if (contentType.includes("json")) json = await response.json();
30
+ else text = (await response.text()).slice(0, 500);
31
+ } catch {
32
+ }
33
+ return {
34
+ ok: response.ok,
35
+ status: response.status,
36
+ redirected: response.redirected,
37
+ url: response.url,
38
+ contentType,
39
+ json,
40
+ text
41
+ };
42
+ } catch (error) {
43
+ return {
44
+ ok: false,
45
+ status: 0,
46
+ redirected: false,
47
+ url: target,
48
+ contentType: "",
49
+ json: null,
50
+ text: null,
51
+ error: error?.message ?? String(error)
52
+ };
53
+ }
54
+ }, { url, init });
55
+ }
56
+ async function pollSession(page, sessionUrl, { timeoutMs = 1e4, intervalMs = 500 } = {}) {
57
+ const started = Date.now();
58
+ while (Date.now() - started < timeoutMs) {
59
+ const sessionResp = await pageRequest(page, sessionUrl);
60
+ if (sessionResp.ok && sessionResp.json?.user?.id) {
61
+ return sessionResp.json;
62
+ }
63
+ await page.waitForTimeout(intervalMs);
64
+ }
65
+ return null;
66
+ }
67
+ function deriveAccountFlags({ experiments, userInfo, asi }) {
68
+ const isEnterprise = userInfo?.is_enterprise === true || experiments?.server_is_enterprise === true;
69
+ const isMax = experiments?.server_is_max === true;
70
+ const canUseComputer = asi?.can_use_computer ?? false;
71
+ const isPro = experiments?.server_is_pro === true || canUseComputer && !isMax && !isEnterprise;
72
+ return { isPro, isMax, isEnterprise, canUseComputer };
73
+ }
74
+ function deriveTier(payload) {
75
+ const { isPro, isMax, isEnterprise } = deriveAccountFlags(payload);
76
+ if (isMax) return "Max";
77
+ if (isEnterprise) return "Enterprise";
78
+ if (isPro) return "Pro";
79
+ return "Authenticated";
80
+ }
81
+ async function collectSessionMetadata(page, origin, opts = {}) {
82
+ const endpoints = buildRuntimeEndpoints(origin);
83
+ const sessionData = opts.sessionData ?? await pollSession(page, endpoints.session, { timeoutMs: opts.sessionTimeoutMs ?? 1e4 });
84
+ if (!sessionData?.user?.id) {
85
+ return {
86
+ sessionData: null,
87
+ models: null,
88
+ asi: null,
89
+ rateLimits: null,
90
+ experiments: null,
91
+ userInfo: null,
92
+ tier: "Authenticated",
93
+ cache: {
94
+ modelsConfig: null,
95
+ rateLimits: null,
96
+ isPro: false,
97
+ isMax: false,
98
+ isEnterprise: false,
99
+ canUseComputer: false
100
+ }
101
+ };
102
+ }
103
+ const [modelsResp, asiResp, rateResp, expResp, userInfoResp] = await Promise.all([
104
+ pageRequest(page, endpoints.models),
105
+ pageRequest(page, endpoints.asi),
106
+ pageRequest(page, endpoints.rateLimits),
107
+ pageRequest(page, endpoints.experiments),
108
+ pageRequest(page, endpoints.userInfo)
109
+ ]);
110
+ const payload = {
111
+ experiments: expResp.ok ? expResp.json : null,
112
+ userInfo: userInfoResp.ok ? userInfoResp.json : null,
113
+ asi: asiResp.ok ? asiResp.json : null
114
+ };
115
+ const flags = deriveAccountFlags(payload);
116
+ return {
117
+ sessionData,
118
+ models: modelsResp.ok ? modelsResp.json : null,
119
+ asi: asiResp.ok ? asiResp.json : null,
120
+ rateLimits: rateResp.ok ? rateResp.json : null,
121
+ experiments: expResp.ok ? expResp.json : null,
122
+ userInfo: userInfoResp.ok ? userInfoResp.json : null,
123
+ tier: deriveTier(payload),
124
+ cache: {
125
+ modelsConfig: modelsResp.ok ? modelsResp.json : null,
126
+ rateLimits: rateResp.ok ? rateResp.json : null,
127
+ isPro: flags.isPro,
128
+ isMax: flags.isMax,
129
+ isEnterprise: flags.isEnterprise,
130
+ canUseComputer: flags.canUseComputer
131
+ }
132
+ };
133
+ }
134
+
135
+ export {
136
+ buildRuntimeEndpoints,
137
+ pageRequest,
138
+ collectSessionMetadata
139
+ };