iobroker.ai-usage 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -15
- package/admin/custom/assets/Components-Bq1fGAd7.js +1 -0
- package/admin/custom/assets/ConfigPanel-DGMF4_qT.js +1 -0
- package/admin/custom/assets/{_virtual_mf-localSharedImportMap___mfe_internal__AiUsageComponentSet__mf_owner__1-CXmnAfNY.js → _virtual_mf-localSharedImportMap___mfe_internal__AiUsageComponentSet__mf_owner__1-DKcsNxEw.js} +1 -1
- package/admin/custom/assets/{_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DwHNIjSR.js → _virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-DzPBJWNQ.js} +1 -1
- package/admin/custom/assets/{_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-qu1s07FY.js → _virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-DEdJInQG.js} +3 -3
- package/admin/custom/assets/{_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-C8-GjKo4.js → _virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-CFn_lBXc.js} +1 -1
- package/admin/custom/assets/{_virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__prebuild___mf_0_iobroker_mf_1_gui_mf_2_components__prebuild__-CQxWwR2r.js → _virtual_mf___mfe_internal__AiUsageComponentSet__mf_owner__1__prebuild___mf_0_iobroker_mf_1_gui_mf_2_components__prebuild__-DeLUTSLT.js} +1 -1
- package/admin/custom/assets/{hostInit-RHMRa462.js → hostInit-DoGxgbHj.js} +1 -1
- package/admin/custom/assets/index-CKngiVu3.js +1 -0
- package/admin/custom/assets/{lib-ByF76TCN.js → lib-MY71LxhX.js} +1 -1
- package/admin/custom/assets/{mf-entry-bootstrap-0-e070fa3e.js → mf-entry-bootstrap-0-eec08a33.js} +2 -2
- package/admin/custom/assets/virtualExposes-Dc9myzsk.js +1 -0
- package/admin/custom/assets/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__AiUsageComponentSet__customComponents_js-l7jtElkM.js → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__AiUsageComponentSet__customComponents_js-CP4YA3u3.js} +1 -1
- package/admin/custom/assets/virtual_mf-exposes-ssr___mfe_internal__AiUsageComponentSet__customComponents_js-DlNU_Z38.js +2 -0
- package/admin/custom/assets/{virtual_mf-exposes___mfe_internal__AiUsageComponentSet__customComponents_js-BZ-CVpVo.js → virtual_mf-exposes___mfe_internal__AiUsageComponentSet__customComponents_js-CEfffc0D.js} +2 -2
- package/admin/custom/customComponents.js +1 -1
- package/admin/custom/i18n/de.json +24 -15
- package/admin/custom/i18n/en.json +25 -16
- package/admin/custom/i18n/es.json +25 -16
- package/admin/custom/i18n/fr.json +25 -16
- package/admin/custom/i18n/it.json +25 -16
- package/admin/custom/i18n/nl.json +25 -16
- package/admin/custom/i18n/pl.json +25 -16
- package/admin/custom/i18n/pt.json +25 -16
- package/admin/custom/i18n/ru.json +25 -16
- package/admin/custom/i18n/uk.json +25 -16
- package/admin/custom/i18n/zh-cn.json +25 -16
- package/admin/custom/mf-manifest.json +1 -1
- package/build/lib/http.js +29 -0
- package/build/lib/http.js.map +2 -2
- package/build/lib/jwt.js +59 -0
- package/build/lib/jwt.js.map +7 -0
- package/build/lib/poll-engine.js +14 -0
- package/build/lib/poll-engine.js.map +2 -2
- package/build/lib/provider.js.map +2 -2
- package/build/lib/providers/chatgpt-auth.js +125 -0
- package/build/lib/providers/chatgpt-auth.js.map +7 -0
- package/build/lib/providers/chatgpt-sub.js +121 -0
- package/build/lib/providers/chatgpt-sub.js.map +7 -0
- package/build/lib/providers/claude-auth.js.map +2 -2
- package/build/lib/providers/claude-sub.js.map +2 -2
- package/build/lib/providers/gemini-auth.js +135 -0
- package/build/lib/providers/gemini-auth.js.map +7 -0
- package/build/lib/providers/gemini-sub.js +147 -0
- package/build/lib/providers/gemini-sub.js.map +7 -0
- package/build/lib/pure-helpers.js +38 -6
- package/build/lib/pure-helpers.js.map +2 -2
- package/build/lib/sign-in.js +38 -0
- package/build/lib/sign-in.js.map +7 -0
- package/build/main.js +283 -151
- package/build/main.js.map +2 -2
- package/io-package.json +28 -12
- package/package.json +1 -1
- package/admin/custom/assets/Components-Bd545388.js +0 -1
- package/admin/custom/assets/ConfigPanel-qivVprTj.js +0 -1
- package/admin/custom/assets/index-B0J1NbOp.js +0 -1
- package/admin/custom/assets/virtualExposes-BZWQ_oUV.js +0 -1
- package/admin/custom/assets/virtual_mf-exposes-ssr___mfe_internal__AiUsageComponentSet__customComponents_js-CRK-nAdE.js +0 -2
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var gemini_auth_exports = {};
|
|
20
|
+
__export(gemini_auth_exports, {
|
|
21
|
+
GEMINI_OAUTH: () => GEMINI_OAUTH,
|
|
22
|
+
buildGeminiAuthorizeUrl: () => buildGeminiAuthorizeUrl,
|
|
23
|
+
exchangeGeminiCode: () => exchangeGeminiCode,
|
|
24
|
+
extractGeminiCode: () => extractGeminiCode,
|
|
25
|
+
generateGeminiPkce: () => generateGeminiPkce,
|
|
26
|
+
refreshGeminiTokens: () => refreshGeminiTokens
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(gemini_auth_exports);
|
|
29
|
+
var import_node_crypto = require("node:crypto");
|
|
30
|
+
var import_provider = require("../provider");
|
|
31
|
+
const GEMINI_OAUTH = {
|
|
32
|
+
clientId: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
|
|
33
|
+
clientSecret: "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",
|
|
34
|
+
authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
35
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
36
|
+
redirectUri: "http://localhost:51121/oauth-callback",
|
|
37
|
+
scopes: [
|
|
38
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
39
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
40
|
+
"https://www.googleapis.com/auth/userinfo.profile",
|
|
41
|
+
"https://www.googleapis.com/auth/cclog",
|
|
42
|
+
"https://www.googleapis.com/auth/experimentsandconfigs"
|
|
43
|
+
],
|
|
44
|
+
fallbackLifetimeMs: 60 * 6e4
|
|
45
|
+
};
|
|
46
|
+
function generateGeminiPkce() {
|
|
47
|
+
const verifier = (0, import_node_crypto.randomBytes)(32).toString("base64url");
|
|
48
|
+
return {
|
|
49
|
+
verifier,
|
|
50
|
+
challenge: (0, import_node_crypto.createHash)("sha256").update(verifier).digest("base64url"),
|
|
51
|
+
state: (0, import_node_crypto.randomBytes)(16).toString("hex")
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function buildGeminiAuthorizeUrl(pkce) {
|
|
55
|
+
const params = new URLSearchParams({
|
|
56
|
+
client_id: GEMINI_OAUTH.clientId,
|
|
57
|
+
redirect_uri: GEMINI_OAUTH.redirectUri,
|
|
58
|
+
response_type: "code",
|
|
59
|
+
scope: GEMINI_OAUTH.scopes.join(" "),
|
|
60
|
+
code_challenge: pkce.challenge,
|
|
61
|
+
code_challenge_method: "S256",
|
|
62
|
+
state: pkce.state,
|
|
63
|
+
access_type: "offline",
|
|
64
|
+
prompt: "consent"
|
|
65
|
+
});
|
|
66
|
+
return `${GEMINI_OAUTH.authorizeUrl}?${params.toString()}`;
|
|
67
|
+
}
|
|
68
|
+
function extractGeminiCode(pasted, expectedState) {
|
|
69
|
+
const trimmed = pasted.trim();
|
|
70
|
+
if (!trimmed) {
|
|
71
|
+
throw new import_provider.FetchError("auth", "nothing pasted");
|
|
72
|
+
}
|
|
73
|
+
if (!trimmed.includes("?") && !trimmed.includes("&")) {
|
|
74
|
+
return trimmed;
|
|
75
|
+
}
|
|
76
|
+
const query = trimmed.slice(trimmed.indexOf("?") + 1);
|
|
77
|
+
const params = new URLSearchParams(query);
|
|
78
|
+
const error = params.get("error");
|
|
79
|
+
if (error) {
|
|
80
|
+
throw new import_provider.FetchError("auth", `Google reported "${error}"`);
|
|
81
|
+
}
|
|
82
|
+
const code = params.get("code");
|
|
83
|
+
if (!code) {
|
|
84
|
+
throw new import_provider.FetchError("auth", "the pasted address carries no code");
|
|
85
|
+
}
|
|
86
|
+
const state = params.get("state");
|
|
87
|
+
if (state && state !== expectedState) {
|
|
88
|
+
throw new import_provider.FetchError("auth", "the pasted address belongs to a different sign-in attempt");
|
|
89
|
+
}
|
|
90
|
+
return code;
|
|
91
|
+
}
|
|
92
|
+
async function exchangeGeminiCode(code, pkce, post, now) {
|
|
93
|
+
const body = await post(GEMINI_OAUTH.tokenUrl, {
|
|
94
|
+
grant_type: "authorization_code",
|
|
95
|
+
client_id: GEMINI_OAUTH.clientId,
|
|
96
|
+
client_secret: GEMINI_OAUTH.clientSecret,
|
|
97
|
+
code,
|
|
98
|
+
code_verifier: pkce.verifier,
|
|
99
|
+
redirect_uri: GEMINI_OAUTH.redirectUri
|
|
100
|
+
});
|
|
101
|
+
return toTokenSet(body, now, "");
|
|
102
|
+
}
|
|
103
|
+
async function refreshGeminiTokens(tokens, post, now) {
|
|
104
|
+
const body = await post(GEMINI_OAUTH.tokenUrl, {
|
|
105
|
+
grant_type: "refresh_token",
|
|
106
|
+
client_id: GEMINI_OAUTH.clientId,
|
|
107
|
+
client_secret: GEMINI_OAUTH.clientSecret,
|
|
108
|
+
refresh_token: tokens.refreshToken
|
|
109
|
+
});
|
|
110
|
+
return toTokenSet(body, now, tokens.refreshToken, tokens.accountRef);
|
|
111
|
+
}
|
|
112
|
+
function toTokenSet(body, now, previousRefresh, previousAccount) {
|
|
113
|
+
const raw = typeof body === "object" && body !== null ? body : {};
|
|
114
|
+
const accessToken = typeof raw.access_token === "string" ? raw.access_token : "";
|
|
115
|
+
if (!accessToken) {
|
|
116
|
+
throw new import_provider.FetchError("auth", "no access token in the answer");
|
|
117
|
+
}
|
|
118
|
+
const lifetime = Number(raw.expires_in);
|
|
119
|
+
return {
|
|
120
|
+
accessToken,
|
|
121
|
+
refreshToken: typeof raw.refresh_token === "string" && raw.refresh_token ? raw.refresh_token : previousRefresh,
|
|
122
|
+
expiresAt: now + (Number.isFinite(lifetime) && lifetime > 0 ? lifetime * 1e3 : GEMINI_OAUTH.fallbackLifetimeMs),
|
|
123
|
+
accountRef: previousAccount
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
127
|
+
0 && (module.exports = {
|
|
128
|
+
GEMINI_OAUTH,
|
|
129
|
+
buildGeminiAuthorizeUrl,
|
|
130
|
+
exchangeGeminiCode,
|
|
131
|
+
extractGeminiCode,
|
|
132
|
+
generateGeminiPkce,
|
|
133
|
+
refreshGeminiTokens
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=gemini-auth.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/providers/gemini-auth.ts"],
|
|
4
|
+
"sourcesContent": ["import { createHash, randomBytes } from \"node:crypto\";\nimport { FetchError, type TokenSet } from \"../provider\";\nimport type { FormPost } from \"./chatgpt-auth\";\n\n/**\n * Constants of the Google sign-in that actually works for consumer subscriptions.\n *\n * The Gemini-CLI client is switched off for individuals (\"no longer supported for\n * Gemini Code Assist for individuals\"), so the Antigravity client is the only one\n * left. Its secret is public in the open source of an installed application \u2014 that\n * is normal for this OAuth flow and not a leak.\n *\n * MEASURED 2026-08-26 against Google (with a control probe, so the test proves\n * something): the device-code flow answers `invalid_client` for BOTH clients, and\n * the paste-code page `codeassist.google.com/authcode` is rejected exactly like an\n * unregistered address. Only the loopback redirect is accepted \u2014 which is why the\n * user has to copy the address of the browser error page back into the admin.\n */\nexport const GEMINI_OAUTH = {\n clientId: \"1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com\",\n clientSecret: \"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\",\n authorizeUrl: \"https://accounts.google.com/o/oauth2/v2/auth\",\n tokenUrl: \"https://oauth2.googleapis.com/token\",\n redirectUri: \"http://localhost:51121/oauth-callback\",\n scopes: [\n \"https://www.googleapis.com/auth/cloud-platform\",\n \"https://www.googleapis.com/auth/userinfo.email\",\n \"https://www.googleapis.com/auth/userinfo.profile\",\n \"https://www.googleapis.com/auth/cclog\",\n \"https://www.googleapis.com/auth/experimentsandconfigs\",\n ],\n fallbackLifetimeMs: 60 * 60_000,\n} as const;\n\n/** The one-time secrets of a running sign-in \u2014 kept in memory only. */\nexport interface GeminiPkce {\n /** The secret half, sent only when redeeming the code. */\n verifier: string;\n /** The public half, sent in the sign-in link. */\n challenge: string;\n /** Ties the pasted answer to this attempt (cross-site check). */\n state: string;\n}\n\n/**\n * Create the PKCE pair and the state for one sign-in attempt.\n *\n * @returns the pair\n */\nexport function generateGeminiPkce(): GeminiPkce {\n const verifier = randomBytes(32).toString(\"base64url\");\n return {\n verifier,\n challenge: createHash(\"sha256\").update(verifier).digest(\"base64url\"),\n state: randomBytes(16).toString(\"hex\"),\n };\n}\n\n/**\n * Build the sign-in link the user opens in a browser.\n *\n * @param pkce the attempt's secrets\n * @returns the authorize URL\n */\nexport function buildGeminiAuthorizeUrl(pkce: GeminiPkce): string {\n const params = new URLSearchParams({\n client_id: GEMINI_OAUTH.clientId,\n redirect_uri: GEMINI_OAUTH.redirectUri,\n response_type: \"code\",\n scope: GEMINI_OAUTH.scopes.join(\" \"),\n code_challenge: pkce.challenge,\n code_challenge_method: \"S256\",\n state: pkce.state,\n access_type: \"offline\",\n prompt: \"consent\",\n });\n return `${GEMINI_OAUTH.authorizeUrl}?${params.toString()}`;\n}\n\n/**\n * Pull code and state out of whatever the user pasted back.\n *\n * Google redirects to a local address where nothing listens, so the browser shows\n * an error page \u2014 the ADDRESS still carries the result. Users paste the whole\n * address; some paste only the code. Both are accepted, but a wrong state is not:\n * that is the cross-site check.\n *\n * @param pasted the pasted address or bare code\n * @param expectedState the state of the running attempt\n * @returns the authorization code\n */\nexport function extractGeminiCode(pasted: string, expectedState: string): string {\n const trimmed = pasted.trim();\n if (!trimmed) {\n throw new FetchError(\"auth\", \"nothing pasted\");\n }\n if (!trimmed.includes(\"?\") && !trimmed.includes(\"&\")) {\n return trimmed;\n }\n const query = trimmed.slice(trimmed.indexOf(\"?\") + 1);\n const params = new URLSearchParams(query);\n const error = params.get(\"error\");\n if (error) {\n throw new FetchError(\"auth\", `Google reported \"${error}\"`);\n }\n const code = params.get(\"code\");\n if (!code) {\n throw new FetchError(\"auth\", \"the pasted address carries no code\");\n }\n const state = params.get(\"state\");\n if (state && state !== expectedState) {\n throw new FetchError(\"auth\", \"the pasted address belongs to a different sign-in attempt\");\n }\n return code;\n}\n\n/**\n * Redeem the code for tokens.\n *\n * @param code the authorization code\n * @param pkce the attempt's secrets\n * @param post the form POST seam\n * @param now current time in ms\n * @returns the token set\n */\nexport async function exchangeGeminiCode(\n code: string,\n pkce: GeminiPkce,\n post: FormPost,\n now: number,\n): Promise<TokenSet> {\n const body = await post(GEMINI_OAUTH.tokenUrl, {\n grant_type: \"authorization_code\",\n client_id: GEMINI_OAUTH.clientId,\n client_secret: GEMINI_OAUTH.clientSecret,\n code,\n code_verifier: pkce.verifier,\n redirect_uri: GEMINI_OAUTH.redirectUri,\n });\n return toTokenSet(body, now, \"\");\n}\n\n/**\n * Get a fresh access token; Google often omits a new refresh token, then the old stays.\n *\n * @param tokens the current tokens\n * @param post the form POST seam\n * @param now current time in ms\n * @returns the refreshed token set\n */\nexport async function refreshGeminiTokens(tokens: TokenSet, post: FormPost, now: number): Promise<TokenSet> {\n const body = await post(GEMINI_OAUTH.tokenUrl, {\n grant_type: \"refresh_token\",\n client_id: GEMINI_OAUTH.clientId,\n client_secret: GEMINI_OAUTH.clientSecret,\n refresh_token: tokens.refreshToken,\n });\n return toTokenSet(body, now, tokens.refreshToken, tokens.accountRef);\n}\n\n/**\n * Build a token set from a Google token answer.\n *\n * @param body the parsed answer\n * @param now current time in ms\n * @param previousRefresh refresh token to keep when the answer omits one\n * @param previousAccount project id to keep\n * @returns the token set\n */\nfunction toTokenSet(body: unknown, now: number, previousRefresh: string, previousAccount?: string): TokenSet {\n const raw = (typeof body === \"object\" && body !== null ? body : {}) as Record<string, unknown>;\n const accessToken = typeof raw.access_token === \"string\" ? raw.access_token : \"\";\n if (!accessToken) {\n throw new FetchError(\"auth\", \"no access token in the answer\");\n }\n const lifetime = Number(raw.expires_in);\n return {\n accessToken,\n refreshToken: typeof raw.refresh_token === \"string\" && raw.refresh_token ? raw.refresh_token : previousRefresh,\n expiresAt: now + (Number.isFinite(lifetime) && lifetime > 0 ? lifetime * 1000 : GEMINI_OAUTH.fallbackLifetimeMs),\n accountRef: previousAccount,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var gemini_sub_exports = {};
|
|
20
|
+
__export(gemini_sub_exports, {
|
|
21
|
+
GEMINI_HOSTS: () => GEMINI_HOSTS,
|
|
22
|
+
GEMINI_IDENTITY: () => GEMINI_IDENTITY,
|
|
23
|
+
geminiSubProvider: () => geminiSubProvider,
|
|
24
|
+
parseCodeAssist: () => parseCodeAssist,
|
|
25
|
+
parseGeminiQuota: () => parseGeminiQuota
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(gemini_sub_exports);
|
|
28
|
+
var import_provider = require("../provider");
|
|
29
|
+
var import_gemini_auth = require("./gemini-auth");
|
|
30
|
+
var import_pure_helpers = require("../pure-helpers");
|
|
31
|
+
const GEMINI_HOSTS = [
|
|
32
|
+
"https://daily-cloudcode-pa.googleapis.com/v1internal",
|
|
33
|
+
"https://cloudcode-pa.googleapis.com/v1internal"
|
|
34
|
+
];
|
|
35
|
+
const GEMINI_IDENTITY = {
|
|
36
|
+
ideType: "ANTIGRAVITY",
|
|
37
|
+
userAgent: "antigravity/1.11.3"
|
|
38
|
+
};
|
|
39
|
+
async function callCodeAssist(method, body, accessToken, post) {
|
|
40
|
+
const headers = {
|
|
41
|
+
Authorization: `Bearer ${accessToken}`,
|
|
42
|
+
"User-Agent": GEMINI_IDENTITY.userAgent
|
|
43
|
+
};
|
|
44
|
+
let lastError;
|
|
45
|
+
for (const host of GEMINI_HOSTS) {
|
|
46
|
+
try {
|
|
47
|
+
return await post(`${host}:${method}`, body, headers);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
if (e instanceof import_provider.FetchError && e.kind === "auth") {
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
lastError = e;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw lastError instanceof Error ? lastError : new import_provider.FetchError("network", "no Code-Assist host answered");
|
|
56
|
+
}
|
|
57
|
+
function parseCodeAssist(body) {
|
|
58
|
+
var _a, _b;
|
|
59
|
+
const raw = typeof body === "object" && body !== null ? body : {};
|
|
60
|
+
const project = typeof raw.cloudaicompanionProject === "string" ? raw.cloudaicompanionProject : "";
|
|
61
|
+
const paid = (_a = raw.paidTier) != null ? _a : {};
|
|
62
|
+
const current = (_b = raw.currentTier) != null ? _b : {};
|
|
63
|
+
const tier = typeof paid.name === "string" && paid.name || typeof current.name === "string" && current.name || typeof paid.id === "string" && paid.id || typeof current.id === "string" && current.id || "";
|
|
64
|
+
return { project, tier };
|
|
65
|
+
}
|
|
66
|
+
function parseGeminiQuota(body) {
|
|
67
|
+
if (typeof body !== "object" || body === null) {
|
|
68
|
+
throw new import_provider.FetchError("network", "unexpected quota response");
|
|
69
|
+
}
|
|
70
|
+
const raw = body;
|
|
71
|
+
const buckets = Array.isArray(raw.buckets) ? raw.buckets : [];
|
|
72
|
+
const limits = [];
|
|
73
|
+
const seen = /* @__PURE__ */ new Set();
|
|
74
|
+
for (const entry of buckets) {
|
|
75
|
+
if (typeof entry !== "object" || entry === null) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const bucket = entry;
|
|
79
|
+
const fraction = Number(bucket.remainingFraction);
|
|
80
|
+
if (!Number.isFinite(fraction)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const model = typeof bucket.modelId === "string" ? bucket.modelId : "";
|
|
84
|
+
const kind = typeof bucket.tokenType === "string" ? bucket.tokenType.toLowerCase() : "";
|
|
85
|
+
const name = (0, import_pure_helpers.sanitizeId)(model || kind || "quota");
|
|
86
|
+
if (!name || seen.has(name)) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
seen.add(name);
|
|
90
|
+
const window = {
|
|
91
|
+
name,
|
|
92
|
+
label: model || kind || "Quota",
|
|
93
|
+
percent: Math.round((1 - Math.min(Math.max(fraction, 0), 1)) * 1e3) / 10
|
|
94
|
+
};
|
|
95
|
+
if (typeof bucket.resetTime === "string" && bucket.resetTime) {
|
|
96
|
+
window.resetAt = bucket.resetTime;
|
|
97
|
+
}
|
|
98
|
+
limits.push(window);
|
|
99
|
+
}
|
|
100
|
+
return limits.length ? { limits } : {};
|
|
101
|
+
}
|
|
102
|
+
function geminiSubProvider(store, post, postForm, now = Date.now) {
|
|
103
|
+
let cached = null;
|
|
104
|
+
return {
|
|
105
|
+
kind: "gemini-sub",
|
|
106
|
+
fetch: async () => {
|
|
107
|
+
cached != null ? cached : cached = await store.load();
|
|
108
|
+
if (!cached) {
|
|
109
|
+
throw new import_provider.FetchError("auth", "not signed in \u2014 start the Google sign-in in the instance settings");
|
|
110
|
+
}
|
|
111
|
+
if (now() >= cached.expiresAt - 6e4) {
|
|
112
|
+
cached = await (0, import_gemini_auth.refreshGeminiTokens)(cached, postForm, now());
|
|
113
|
+
await store.save(cached);
|
|
114
|
+
}
|
|
115
|
+
if (!cached.accountRef) {
|
|
116
|
+
const info = parseCodeAssist(
|
|
117
|
+
await callCodeAssist(
|
|
118
|
+
"loadCodeAssist",
|
|
119
|
+
{ metadata: { ideType: GEMINI_IDENTITY.ideType } },
|
|
120
|
+
cached.accessToken,
|
|
121
|
+
post
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
if (!info.project) {
|
|
125
|
+
throw new import_provider.FetchError(
|
|
126
|
+
"auth",
|
|
127
|
+
"Google returned no project for this account \u2014 a Google AI subscription (Pro/Ultra) is required"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
cached = { ...cached, accountRef: info.project };
|
|
131
|
+
await store.save(cached);
|
|
132
|
+
}
|
|
133
|
+
return parseGeminiQuota(
|
|
134
|
+
await callCodeAssist("retrieveUserQuota", { project: cached.accountRef }, cached.accessToken, post)
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
140
|
+
0 && (module.exports = {
|
|
141
|
+
GEMINI_HOSTS,
|
|
142
|
+
GEMINI_IDENTITY,
|
|
143
|
+
geminiSubProvider,
|
|
144
|
+
parseCodeAssist,
|
|
145
|
+
parseGeminiQuota
|
|
146
|
+
});
|
|
147
|
+
//# sourceMappingURL=gemini-sub.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/providers/gemini-sub.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n FetchError,\n type LimitWindow,\n type TokenSet,\n type TokenStore,\n type UsageProvider,\n type UsageSnapshot,\n} from \"../provider\";\nimport type { FormPost } from \"./chatgpt-auth\";\nimport { refreshGeminiTokens } from \"./gemini-auth\";\nimport { sanitizeId } from \"../pure-helpers\";\n\n/**\n * Google's internal Code-Assist endpoints. The `daily-` host is tried first:\n * two independent measurements report 429 on the plain host and 200 on `daily-`,\n * so the plain one stays as a fallback rather than as the default.\n */\nexport const GEMINI_HOSTS = [\n \"https://daily-cloudcode-pa.googleapis.com/v1internal\",\n \"https://cloudcode-pa.googleapis.com/v1internal\",\n] as const;\n\n/**\n * The identity that decides WHICH quota buckets Google returns. Sending the\n * Gemini-CLI identity to a paid account yields the retired free-tier set, where\n * every bucket sits at 100 % forever \u2014 a counter that never moves is worse than\n * an error, so both calls must carry this one identity.\n */\nexport const GEMINI_IDENTITY = {\n ideType: \"ANTIGRAVITY\",\n userAgent: \"antigravity/1.11.3\",\n} as const;\n\n/** A JSON POST seam that also reports which host answered. */\nexport type GeminiPost = (\n url: string,\n body: Record<string, unknown>,\n headers?: Record<string, string>,\n) => Promise<unknown>;\n\n/**\n * Call a Code-Assist method, falling back to the second host on failure.\n *\n * @param method the method name (loadCodeAssist, retrieveUserQuota)\n * @param body the request body\n * @param accessToken the bearer token\n * @param post the POST seam\n * @returns the parsed answer\n */\nasync function callCodeAssist(\n method: string,\n body: Record<string, unknown>,\n accessToken: string,\n post: GeminiPost,\n): Promise<unknown> {\n const headers = {\n Authorization: `Bearer ${accessToken}`,\n \"User-Agent\": GEMINI_IDENTITY.userAgent,\n };\n let lastError: unknown;\n for (const host of GEMINI_HOSTS) {\n try {\n return await post(`${host}:${method}`, body, headers);\n } catch (e) {\n // An auth failure is the same on every host \u2014 only retry transport trouble.\n if (e instanceof FetchError && e.kind === \"auth\") {\n throw e;\n }\n lastError = e;\n }\n }\n throw lastError instanceof Error ? lastError : new FetchError(\"network\", \"no Code-Assist host answered\");\n}\n\n/**\n * Read the project id and the tier out of a `loadCodeAssist` answer.\n *\n * @param body the parsed answer\n * @returns project id and tier label\n */\nexport function parseCodeAssist(body: unknown): { project: string; tier: string } {\n const raw = (typeof body === \"object\" && body !== null ? body : {}) as Record<string, unknown>;\n const project = typeof raw.cloudaicompanionProject === \"string\" ? raw.cloudaicompanionProject : \"\";\n const paid = (raw.paidTier ?? {}) as Record<string, unknown>;\n const current = (raw.currentTier ?? {}) as Record<string, unknown>;\n const tier =\n (typeof paid.name === \"string\" && paid.name) ||\n (typeof current.name === \"string\" && current.name) ||\n (typeof paid.id === \"string\" && paid.id) ||\n (typeof current.id === \"string\" && current.id) ||\n \"\";\n return { project, tier };\n}\n\n/**\n * Turn a `retrieveUserQuota` answer into limit windows.\n *\n * Google reports what is LEFT (`remainingFraction` 0\u20261); the tree shows utilisation,\n * so it is inverted here. A bucket without a usable fraction is skipped rather than\n * guessed \u2014 an invented 0 % would read as \"nothing used yet\".\n *\n * @param body the parsed answer\n * @returns the snapshot\n */\nexport function parseGeminiQuota(body: unknown): UsageSnapshot {\n if (typeof body !== \"object\" || body === null) {\n throw new FetchError(\"network\", \"unexpected quota response\");\n }\n const raw = body as Record<string, unknown>;\n const buckets = Array.isArray(raw.buckets) ? raw.buckets : [];\n const limits: LimitWindow[] = [];\n const seen = new Set<string>();\n for (const entry of buckets) {\n if (typeof entry !== \"object\" || entry === null) {\n continue;\n }\n const bucket = entry as Record<string, unknown>;\n const fraction = Number(bucket.remainingFraction);\n if (!Number.isFinite(fraction)) {\n continue;\n }\n const model = typeof bucket.modelId === \"string\" ? bucket.modelId : \"\";\n const kind = typeof bucket.tokenType === \"string\" ? bucket.tokenType.toLowerCase() : \"\";\n const name = sanitizeId(model || kind || \"quota\");\n if (!name || seen.has(name)) {\n continue;\n }\n seen.add(name);\n const window: LimitWindow = {\n name,\n label: model || kind || \"Quota\",\n percent: Math.round((1 - Math.min(Math.max(fraction, 0), 1)) * 1000) / 10,\n };\n if (typeof bucket.resetTime === \"string\" && bucket.resetTime) {\n window.resetAt = bucket.resetTime;\n }\n limits.push(window);\n }\n return limits.length ? { limits } : {};\n}\n\n/**\n * The Google/Gemini subscription provider.\n *\n * @param store where the tokens live\n * @param post the JSON POST seam\n * @param postForm the form POST seam (token refresh)\n * @param now clock (ms)\n * @returns the provider\n */\nexport function geminiSubProvider(\n store: TokenStore,\n post: GeminiPost,\n postForm: FormPost,\n now: () => number = Date.now,\n): UsageProvider {\n let cached: TokenSet | null = null;\n return {\n kind: \"gemini-sub\",\n fetch: async (): Promise<UsageSnapshot> => {\n cached ??= await store.load();\n if (!cached) {\n throw new FetchError(\"auth\", \"not signed in \u2014 start the Google sign-in in the instance settings\");\n }\n if (now() >= cached.expiresAt - 60_000) {\n cached = await refreshGeminiTokens(cached, postForm, now());\n await store.save(cached);\n }\n // The project id is stable per account \u2014 look it up once, then reuse it.\n if (!cached.accountRef) {\n const info = parseCodeAssist(\n await callCodeAssist(\n \"loadCodeAssist\",\n { metadata: { ideType: GEMINI_IDENTITY.ideType } },\n cached.accessToken,\n post,\n ),\n );\n if (!info.project) {\n throw new FetchError(\n \"auth\",\n \"Google returned no project for this account \u2014 a Google AI subscription (Pro/Ultra) is required\",\n );\n }\n cached = { ...cached, accountRef: info.project };\n await store.save(cached);\n }\n return parseGeminiQuota(\n await callCodeAssist(\"retrieveUserQuota\", { project: cached.accountRef }, cached.accessToken, post),\n );\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -20,6 +20,9 @@ var pure_helpers_exports = {};
|
|
|
20
20
|
__export(pure_helpers_exports, {
|
|
21
21
|
PROVIDER_KINDS: () => PROVIDER_KINDS,
|
|
22
22
|
RESERVED_ROOT_IDS: () => RESERVED_ROOT_IDS,
|
|
23
|
+
SUBSCRIPTION_IDS: () => SUBSCRIPTION_IDS,
|
|
24
|
+
SUBSCRIPTION_KINDS: () => SUBSCRIPTION_KINDS,
|
|
25
|
+
accountId: () => accountId,
|
|
23
26
|
clampPollInterval: () => clampPollInterval,
|
|
24
27
|
parseAccounts: () => parseAccounts,
|
|
25
28
|
sanitizeId: () => sanitizeId,
|
|
@@ -29,8 +32,30 @@ module.exports = __toCommonJS(pure_helpers_exports);
|
|
|
29
32
|
function sanitizeId(name) {
|
|
30
33
|
return name.trim().replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_{2,}/g, "_").replace(/^_+|_+$/g, "");
|
|
31
34
|
}
|
|
32
|
-
const PROVIDER_KINDS = [
|
|
33
|
-
|
|
35
|
+
const PROVIDER_KINDS = [
|
|
36
|
+
"claude-sub",
|
|
37
|
+
"chatgpt-sub",
|
|
38
|
+
"gemini-sub",
|
|
39
|
+
"openrouter",
|
|
40
|
+
"deepseek",
|
|
41
|
+
"openai",
|
|
42
|
+
"anthropic-api"
|
|
43
|
+
];
|
|
44
|
+
const SUBSCRIPTION_KINDS = ["claude-sub", "chatgpt-sub", "gemini-sub"];
|
|
45
|
+
const SUBSCRIPTION_IDS = {
|
|
46
|
+
"claude-sub": "claude",
|
|
47
|
+
"chatgpt-sub": "chatgpt",
|
|
48
|
+
"gemini-sub": "gemini"
|
|
49
|
+
};
|
|
50
|
+
const RESERVED_ROOT_IDS = ["info", "total"];
|
|
51
|
+
function accountId(provider, credentialId) {
|
|
52
|
+
const fixed = SUBSCRIPTION_IDS[provider];
|
|
53
|
+
if (fixed) {
|
|
54
|
+
return fixed;
|
|
55
|
+
}
|
|
56
|
+
const suffix = sanitizeId(credentialId.replace(/^system\.credentials\./, ""));
|
|
57
|
+
return suffix ? `${suffix}-api` : "";
|
|
58
|
+
}
|
|
34
59
|
function parseAccounts(raw) {
|
|
35
60
|
if (!Array.isArray(raw)) {
|
|
36
61
|
return [];
|
|
@@ -45,9 +70,10 @@ function parseAccounts(raw) {
|
|
|
45
70
|
if (row.enabled === false) {
|
|
46
71
|
continue;
|
|
47
72
|
}
|
|
48
|
-
const name = typeof row.name === "string" ? row.name.trim() : "";
|
|
49
|
-
const id = sanitizeId(name);
|
|
50
73
|
const provider = typeof row.provider === "string" ? row.provider : "";
|
|
74
|
+
const credentialId = typeof row.credentialId === "string" ? row.credentialId : "";
|
|
75
|
+
const id = accountId(provider, credentialId);
|
|
76
|
+
const name = (typeof row.name === "string" ? row.name.trim() : "") || id;
|
|
51
77
|
if (!id || RESERVED_ROOT_IDS.includes(id) || !PROVIDER_KINDS.includes(provider) || seen.has(id)) {
|
|
52
78
|
continue;
|
|
53
79
|
}
|
|
@@ -57,7 +83,7 @@ function parseAccounts(raw) {
|
|
|
57
83
|
name,
|
|
58
84
|
id,
|
|
59
85
|
provider,
|
|
60
|
-
credentialId
|
|
86
|
+
credentialId,
|
|
61
87
|
warnThreshold: Number.isFinite(threshold) && threshold >= 10 && threshold <= 100 ? threshold : 80
|
|
62
88
|
});
|
|
63
89
|
}
|
|
@@ -73,7 +99,10 @@ function validAccountIds(raw) {
|
|
|
73
99
|
continue;
|
|
74
100
|
}
|
|
75
101
|
const row = entry;
|
|
76
|
-
const id =
|
|
102
|
+
const id = accountId(
|
|
103
|
+
typeof row.provider === "string" ? row.provider : "",
|
|
104
|
+
typeof row.credentialId === "string" ? row.credentialId : ""
|
|
105
|
+
);
|
|
77
106
|
if (id && !RESERVED_ROOT_IDS.includes(id) && !ids.includes(id)) {
|
|
78
107
|
ids.push(id);
|
|
79
108
|
}
|
|
@@ -91,6 +120,9 @@ function clampPollInterval(raw) {
|
|
|
91
120
|
0 && (module.exports = {
|
|
92
121
|
PROVIDER_KINDS,
|
|
93
122
|
RESERVED_ROOT_IDS,
|
|
123
|
+
SUBSCRIPTION_IDS,
|
|
124
|
+
SUBSCRIPTION_KINDS,
|
|
125
|
+
accountId,
|
|
94
126
|
clampPollInterval,
|
|
95
127
|
parseAccounts,
|
|
96
128
|
sanitizeId,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/pure-helpers.ts"],
|
|
4
|
-
"sourcesContent": ["/** One configured AI account, validated from the admin table (API boundary). */\nexport interface AccountConfig {\n /** Display name from the table. */\n name: string;\n /** Id-safe object id derived from the name. */\n id: string;\n /** Provider kind. */\n provider: string;\n /** Central credential id (system.credentials.*); empty for claude-sub. */\n credentialId: string;\n /** Warn threshold in percent. */\n warnThreshold: number;\n}\n\n/**\n * Make a string safe as an ioBroker object-id segment.\n *\n * @param name the raw name\n * @returns the sanitized id segment\n */\nexport function sanitizeId(name: string): string {\n return name\n .trim()\n .replace(/[^a-zA-Z0-9_-]/g, \"_\")\n .replace(/_{2,}/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n}\n\n/** The provider kinds the adapter knows. */\nexport const PROVIDER_KINDS = [\"claude-sub\"
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBO,SAAS,WAAW,MAAsB;AAC/C,SAAO,KACJ,KAAK,EACL,QAAQ,mBAAmB,GAAG,EAC9B,QAAQ,UAAU,GAAG,EACrB,QAAQ,YAAY,EAAE;AAC3B;AAGO,MAAM,iBAAiB,CAAC,cAAc,
|
|
4
|
+
"sourcesContent": ["/** One configured AI account, validated from the admin table (API boundary). */\nexport interface AccountConfig {\n /** Display name from the table. */\n name: string;\n /** Id-safe object id derived from the name. */\n id: string;\n /** Provider kind. */\n provider: string;\n /** Central credential id (system.credentials.*); empty for claude-sub. */\n credentialId: string;\n /** Warn threshold in percent. */\n warnThreshold: number;\n}\n\n/**\n * Make a string safe as an ioBroker object-id segment.\n *\n * @param name the raw name\n * @returns the sanitized id segment\n */\nexport function sanitizeId(name: string): string {\n return name\n .trim()\n .replace(/[^a-zA-Z0-9_-]/g, \"_\")\n .replace(/_{2,}/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n}\n\n/** The provider kinds the adapter knows. */\nexport const PROVIDER_KINDS = [\n \"claude-sub\",\n \"chatgpt-sub\",\n \"gemini-sub\",\n \"openrouter\",\n \"deepseek\",\n \"openai\",\n \"anthropic-api\",\n];\n\n/** The subscription kinds: signed in with the user's account, no key involved. */\nexport const SUBSCRIPTION_KINDS = [\"claude-sub\", \"chatgpt-sub\", \"gemini-sub\"];\n\n/** Fixed object id per subscription \u2014 adapter-owned, never derived from a display name. */\nexport const SUBSCRIPTION_IDS: Record<string, string> = {\n \"claude-sub\": \"claude\",\n \"chatgpt-sub\": \"chatgpt\",\n \"gemini-sub\": \"gemini\",\n};\n\n/** Top-level object roots the adapter owns \u2014 no account may use them as id. */\nexport const RESERVED_ROOT_IDS = [\"info\", \"total\"];\n\n/**\n * The object id of one account. Deterministic and stable: a subscription always owns\n * its provider id, a key-based account always carries the \"-api\" suffix. Neither\n * depends on a display name, so an id never moves when the user renames something\n * or adds an unrelated credential later.\n *\n * @param provider the provider kind\n * @param credentialId the central credential id (key-based accounts)\n * @returns the id-safe object id, or \"\" when it cannot be formed\n */\nexport function accountId(provider: string, credentialId: string): string {\n const fixed = SUBSCRIPTION_IDS[provider];\n if (fixed) {\n return fixed;\n }\n const suffix = sanitizeId(credentialId.replace(/^system\\.credentials\\./, \"\"));\n return suffix ? `${suffix}-api` : \"\";\n}\n\n/**\n * Parse and validate the admin accounts table. Rows without a usable name or with an\n * unknown provider are skipped (type-guarded \u2014 the table is external input); disabled\n * rows are skipped too. Duplicate ids keep the first row.\n *\n * @param raw the native.accounts value\n * @returns the validated, enabled accounts\n */\nexport function parseAccounts(raw: unknown): AccountConfig[] {\n if (!Array.isArray(raw)) {\n return [];\n }\n const accounts: AccountConfig[] = [];\n const seen = new Set<string>();\n for (const entry of raw) {\n if (typeof entry !== \"object\" || entry === null) {\n continue;\n }\n const row = entry as Record<string, unknown>;\n if (row.enabled === false) {\n continue;\n }\n const provider = typeof row.provider === \"string\" ? row.provider : \"\";\n const credentialId = typeof row.credentialId === \"string\" ? row.credentialId : \"\";\n const id = accountId(provider, credentialId);\n const name = (typeof row.name === \"string\" ? row.name.trim() : \"\") || id;\n if (!id || RESERVED_ROOT_IDS.includes(id) || !PROVIDER_KINDS.includes(provider) || seen.has(id)) {\n continue;\n }\n seen.add(id);\n const threshold = Number(row.warnThreshold);\n accounts.push({\n name,\n id,\n provider,\n credentialId,\n warnThreshold: Number.isFinite(threshold) && threshold >= 10 && threshold <= 100 ? threshold : 80,\n });\n }\n return accounts;\n}\n\n/**\n * The ids of ALL valid table rows \u2014 including disabled ones. Used by the stale-object\n * cleanup: a disabled account is paused, not deleted; only rows removed from the\n * table lose their tree.\n *\n * @param raw the native.accounts value\n * @returns the id-safe ids of every valid row\n */\nexport function validAccountIds(raw: unknown): string[] {\n if (!Array.isArray(raw)) {\n return [];\n }\n const ids: string[] = [];\n for (const entry of raw) {\n if (typeof entry !== \"object\" || entry === null) {\n continue;\n }\n const row = entry as Record<string, unknown>;\n const id = accountId(\n typeof row.provider === \"string\" ? row.provider : \"\",\n typeof row.credentialId === \"string\" ? row.credentialId : \"\",\n );\n if (id && !RESERVED_ROOT_IDS.includes(id) && !ids.includes(id)) {\n ids.push(id);\n }\n }\n return ids;\n}\n\n/**\n * Clamp the poll interval to the safe range: minimum 60 s (provider throttling locks\n * whole accounts), default 300 s for anything unusable.\n *\n * @param raw the configured value\n * @returns the effective interval in seconds\n */\nexport function clampPollInterval(raw: unknown): number {\n const value = Number(raw);\n if (!Number.isFinite(value) || value <= 0) {\n return 300;\n }\n return Math.min(3600, Math.max(60, Math.round(value)));\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBO,SAAS,WAAW,MAAsB;AAC/C,SAAO,KACJ,KAAK,EACL,QAAQ,mBAAmB,GAAG,EAC9B,QAAQ,UAAU,GAAG,EACrB,QAAQ,YAAY,EAAE;AAC3B;AAGO,MAAM,iBAAiB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,MAAM,qBAAqB,CAAC,cAAc,eAAe,YAAY;AAGrE,MAAM,mBAA2C;AAAA,EACtD,cAAc;AAAA,EACd,eAAe;AAAA,EACf,cAAc;AAChB;AAGO,MAAM,oBAAoB,CAAC,QAAQ,OAAO;AAY1C,SAAS,UAAU,UAAkB,cAA8B;AACxE,QAAM,QAAQ,iBAAiB,QAAQ;AACvC,MAAI,OAAO;AACT,WAAO;AAAA,EACT;AACA,QAAM,SAAS,WAAW,aAAa,QAAQ,0BAA0B,EAAE,CAAC;AAC5E,SAAO,SAAS,GAAG,MAAM,SAAS;AACpC;AAUO,SAAS,cAAc,KAA+B;AAC3D,MAAI,CAAC,MAAM,QAAQ,GAAG,GAAG;AACvB,WAAO,CAAC;AAAA,EACV;AACA,QAAM,WAA4B,CAAC;AACnC,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,SAAS,KAAK;AACvB,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C;AAAA,IACF;AACA,UAAM,MAAM;AACZ,QAAI,IAAI,YAAY,OAAO;AACzB;AAAA,IACF;AACA,UAAM,WAAW,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;AACnE,UAAM,eAAe,OAAO,IAAI,iBAAiB,WAAW,IAAI,eAAe;AAC/E,UAAM,KAAK,UAAU,UAAU,YAAY;AAC3C,UAAM,QAAQ,OAAO,IAAI,SAAS,WAAW,IAAI,KAAK,KAAK,IAAI,OAAO;AACtE,QAAI,CAAC,MAAM,kBAAkB,SAAS,EAAE,KAAK,CAAC,eAAe,SAAS,QAAQ,KAAK,KAAK,IAAI,EAAE,GAAG;AAC/F;AAAA,IACF;AACA,SAAK,IAAI,EAAE;AACX,UAAM,YAAY,OAAO,IAAI,aAAa;AAC1C,aAAS,KAAK;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,eAAe,OAAO,SAAS,SAAS,KAAK,aAAa,MAAM,aAAa,MAAM,YAAY;AAAA,IACjG,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAUO,SAAS,gBAAgB,KAAwB;AACtD,MAAI,CAAC,MAAM,QAAQ,GAAG,GAAG;AACvB,WAAO,CAAC;AAAA,EACV;AACA,QAAM,MAAgB,CAAC;AACvB,aAAW,SAAS,KAAK;AACvB,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C;AAAA,IACF;AACA,UAAM,MAAM;AACZ,UAAM,KAAK;AAAA,MACT,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;AAAA,MAClD,OAAO,IAAI,iBAAiB,WAAW,IAAI,eAAe;AAAA,IAC5D;AACA,QAAI,MAAM,CAAC,kBAAkB,SAAS,EAAE,KAAK,CAAC,IAAI,SAAS,EAAE,GAAG;AAC9D,UAAI,KAAK,EAAE;AAAA,IACb;AAAA,EACF;AACA,SAAO;AACT;AASO,SAAS,kBAAkB,KAAsB;AACtD,QAAM,QAAQ,OAAO,GAAG;AACxB,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,GAAG;AACzC,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,MAAM,KAAK,CAAC,CAAC;AACvD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var sign_in_exports = {};
|
|
20
|
+
__export(sign_in_exports, {
|
|
21
|
+
SIGN_IN_FLOWS: () => SIGN_IN_FLOWS,
|
|
22
|
+
attemptExpired: () => attemptExpired
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(sign_in_exports);
|
|
25
|
+
const SIGN_IN_FLOWS = {
|
|
26
|
+
"claude-sub": "paste-code",
|
|
27
|
+
"chatgpt-sub": "device-code",
|
|
28
|
+
"gemini-sub": "paste-url"
|
|
29
|
+
};
|
|
30
|
+
function attemptExpired(expiresAt, now) {
|
|
31
|
+
return now >= expiresAt;
|
|
32
|
+
}
|
|
33
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
34
|
+
0 && (module.exports = {
|
|
35
|
+
SIGN_IN_FLOWS,
|
|
36
|
+
attemptExpired
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=sign-in.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/sign-in.ts"],
|
|
4
|
+
"sourcesContent": ["import type { SignInFlow } from \"./provider\";\n\n/** Which sign-in flow a subscription uses \u2014 see {@link SignInFlow} for why they differ. */\nexport const SIGN_IN_FLOWS: Record<string, SignInFlow> = {\n \"claude-sub\": \"paste-code\",\n \"chatgpt-sub\": \"device-code\",\n \"gemini-sub\": \"paste-url\",\n};\n\n/** The live state of one subscription row, as the admin panel renders it. */\nexport type SignInState =\n /** Signed in, tokens usable. */\n | { status: \"signed-in\" }\n /** Nothing started yet. */\n | { status: \"signed-out\" }\n /** Claude/Gemini: the user has to open the link and paste something back. */\n | { status: \"awaiting-paste\"; url: string; flow: SignInFlow }\n /** ChatGPT: the user types this code on the provider's page; the adapter waits. */\n | { status: \"awaiting-device\"; userCode: string; verificationUrl: string; expiresAt: number }\n /** The last attempt failed \u2014 reason is user-facing. */\n | { status: \"failed\"; reason: string };\n\n/**\n * Whether a running sign-in attempt is over.\n *\n * @param expiresAt end of the attempt window (ms since epoch)\n * @param now current time (ms)\n * @returns true when the attempt can no longer succeed\n */\nexport function attemptExpired(expiresAt: number, now: number): boolean {\n return now >= expiresAt;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,MAAM,gBAA4C;AAAA,EACvD,cAAc;AAAA,EACd,eAAe;AAAA,EACf,cAAc;AAChB;AAsBO,SAAS,eAAe,WAAmB,KAAsB;AACtE,SAAO,OAAO;AAChB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|