dsh-agy 0.2.3 → 0.2.4
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 +18 -0
- package/lib/{accounts-BvX5zmPa.mjs → accounts-DbTTxX_z.mjs} +102 -44
- package/lib/accounts-DbTTxX_z.mjs.map +1 -0
- package/lib/catalog-iuikPZIb.mjs +170 -0
- package/lib/catalog-iuikPZIb.mjs.map +1 -0
- package/lib/cli/index.d.mts.map +1 -1
- package/lib/cli/index.mjs +115 -11
- package/lib/cli/index.mjs.map +1 -1
- package/lib/{exchange-CteHnRzO.mjs → exchange-BvrLu2E4.mjs} +3 -2
- package/lib/{exchange-CteHnRzO.mjs.map → exchange-BvrLu2E4.mjs.map} +1 -1
- package/lib/{import-C7EV_tne.mjs → import-DSvpbB0T.mjs} +19 -5
- package/lib/import-DSvpbB0T.mjs.map +1 -0
- package/lib/index.mjs +2 -2
- package/lib/models-DRWVi33p.mjs +136 -0
- package/lib/models-DRWVi33p.mjs.map +1 -0
- package/lib/{plugin-common-Bmz4Ife-.mjs → plugin-common-D7lc-W6b.mjs} +46 -6
- package/lib/plugin-common-D7lc-W6b.mjs.map +1 -0
- package/lib/proxy-DQPIwUov.mjs +269 -0
- package/lib/proxy-DQPIwUov.mjs.map +1 -0
- package/lib/{translate-C1SMdm5J.mjs → translate-COjBTH4-.mjs} +27 -6
- package/lib/translate-COjBTH4-.mjs.map +1 -0
- package/lib/web/plugin.mjs +182 -7
- package/lib/web/plugin.mjs.map +1 -1
- package/package.json +4 -1
- package/lib/accounts-BvX5zmPa.mjs.map +0 -1
- package/lib/import-C7EV_tne.mjs.map +0 -1
- package/lib/models-I1UAEk9x.mjs +0 -222
- package/lib/models-I1UAEk9x.mjs.map +0 -1
- package/lib/plugin-common-Bmz4Ife-.mjs.map +0 -1
- package/lib/translate-C1SMdm5J.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -81,6 +81,24 @@ dsh-agy logout # remove account
|
|
|
81
81
|
| `dsh-agy health` | `--index <n...>` — check only these accounts (default: all enabled)<br>`--interval <ms>` — repeat on an interval instead of once | Batch health check (refresh + userinfo), re-enables accounts whose credentials are live again |
|
|
82
82
|
| `dsh-agy logout` | `--index <n>` — account index (default: active)<br>`--email <email>` — account email | Remove an account |
|
|
83
83
|
|
|
84
|
+
### Per-account proxy
|
|
85
|
+
|
|
86
|
+
Each account can have its own proxy; credentials are encrypted at rest and displayed masked as `protocol//host:port`.
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
dsh-agy login --proxy socks5://user:pass@host:1080
|
|
90
|
+
dsh-agy import --proxy <url> file.json
|
|
91
|
+
dsh-agy proxy set --index 0 --proxy <url> # set / update
|
|
92
|
+
dsh-agy proxy clear --index 0 # clear (fall back to env)
|
|
93
|
+
dsh-agy proxy test --index 0 # TCP 2s fast-fail probe
|
|
94
|
+
dsh-agy proxy list # masked list
|
|
95
|
+
dsh-agy status # shows proxy column (masked host:port)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Fallback: with no per-account proxy, requests use `EnvHttpProxyAgent` (`HTTP_PROXY`/`HTTPS_PROXY` with `NO_PROXY` honored). Per-account proxies ignore `NO_PROXY`, are fail-closed (unreachable proxy skips the account without cooldown and clears affinity), and loopback targets (`localhost`/`127.0.0.1`/`::1`) are always forced direct.
|
|
99
|
+
|
|
100
|
+
Web dashboard (`/agy`): each account card shows an inline Proxy row `[input] [Save][Clear][Test]` with masked `host:port`; writes via `POST /agy/api/proxy`, probes via `POST /agy/api/proxy/test`.
|
|
101
|
+
|
|
84
102
|
### Path C: Local Development & Link
|
|
85
103
|
|
|
86
104
|
```sh
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { i as proxiedFetch, n as isProxyUnreachableError } from "./proxy-DQPIwUov.mjs";
|
|
2
3
|
import { createCipheriv, createDecipheriv, createHash, randomBytes, randomUUID } from "node:crypto";
|
|
3
|
-
import { EnvHttpProxyAgent } from "undici";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { dirname, join, resolve } from "node:path";
|
|
6
6
|
import lockfile from "proper-lockfile";
|
|
@@ -32,21 +32,6 @@ var AgyPoolBlockedError = class extends Error {
|
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
//#endregion
|
|
35
|
-
//#region src/proxy.ts
|
|
36
|
-
/**
|
|
37
|
-
* Proxy-aware fetch: respects the standard HTTP_PROXY / HTTPS_PROXY / NO_PROXY
|
|
38
|
-
* environment variables (lowercase variants included) via undici's
|
|
39
|
-
* EnvHttpProxyAgent. Applied per-request through the `dispatcher` option so the
|
|
40
|
-
* plugin never mutates the host process's global dispatcher — DSH's own fetch
|
|
41
|
-
* calls stay untouched.
|
|
42
|
-
*/
|
|
43
|
-
const agent = new EnvHttpProxyAgent();
|
|
44
|
-
/** fetch() that routes through the env-configured proxy when one is set. */
|
|
45
|
-
const proxiedFetch = (input, init) => fetch(input, {
|
|
46
|
-
...init,
|
|
47
|
-
dispatcher: agent
|
|
48
|
-
});
|
|
49
|
-
//#endregion
|
|
50
35
|
//#region src/oauth/constants.ts
|
|
51
36
|
/**
|
|
52
37
|
* Antigravity (agy) OAuth and API constants.
|
|
@@ -144,7 +129,10 @@ async function fetchAgyFirstOk(urlPath, init, fetchImpl = proxiedFetch) {
|
|
|
144
129
|
const response = await fetchImpl(`${baseEndpoint}${urlPath}`, init);
|
|
145
130
|
if (!AGY_ENDPOINT_SKIP_STATUSES.has(response.status)) return response;
|
|
146
131
|
lastSkipped = response;
|
|
147
|
-
} catch {
|
|
132
|
+
} catch (error) {
|
|
133
|
+
const { isProxyUnreachableError } = await import("./proxy-DQPIwUov.mjs").then((n) => n.o);
|
|
134
|
+
if (isProxyUnreachableError(error)) throw error;
|
|
135
|
+
}
|
|
148
136
|
if (lastSkipped) return lastSkipped;
|
|
149
137
|
throw new Error("all agy endpoints failed");
|
|
150
138
|
}
|
|
@@ -410,7 +398,7 @@ async function refreshAccessToken(auth, options) {
|
|
|
410
398
|
client_id: clientId,
|
|
411
399
|
client_secret: clientSecret
|
|
412
400
|
})
|
|
413
|
-
});
|
|
401
|
+
}, options?.proxyUrl ? { proxyUrl: options.proxyUrl } : void 0);
|
|
414
402
|
if (!response.ok) {
|
|
415
403
|
const errorText = await response.text().catch(() => void 0);
|
|
416
404
|
const { code, description } = parseOAuthErrorPayload(errorText);
|
|
@@ -449,13 +437,22 @@ async function refreshAccessToken(auth, options) {
|
|
|
449
437
|
clientId
|
|
450
438
|
};
|
|
451
439
|
} catch (error) {
|
|
440
|
+
const raw = error instanceof Error ? error : new Error(String(error));
|
|
441
|
+
const code = error?.code;
|
|
442
|
+
const errorCode = error?.errorCode;
|
|
443
|
+
const wrapped = new AgyTokenRefreshError({
|
|
444
|
+
message: raw.message,
|
|
445
|
+
status: 0,
|
|
446
|
+
statusText: "Network Error",
|
|
447
|
+
code: code ?? errorCode,
|
|
448
|
+
description: raw.message
|
|
449
|
+
});
|
|
450
|
+
wrapped.cause = error;
|
|
451
|
+
if (code) wrapped.code = code;
|
|
452
|
+
if (errorCode) wrapped.errorCode = errorCode;
|
|
452
453
|
transientFailure = {
|
|
453
454
|
type: "failed",
|
|
454
|
-
error:
|
|
455
|
-
message: error instanceof Error ? error.message : "Unknown refresh error",
|
|
456
|
-
status: 0,
|
|
457
|
-
statusText: "Network Error"
|
|
458
|
-
})
|
|
455
|
+
error: wrapped
|
|
459
456
|
};
|
|
460
457
|
}
|
|
461
458
|
if (transientFailure) return transientFailure;
|
|
@@ -584,6 +581,10 @@ function decideRotation(kind, account, consecutiveFailures, retryAfterMs, catego
|
|
|
584
581
|
action: "retry",
|
|
585
582
|
backoffMs
|
|
586
583
|
};
|
|
584
|
+
case "proxy-unreachable": return {
|
|
585
|
+
action: "rotate",
|
|
586
|
+
backoffMs: Math.min(backoffMs, 1e3)
|
|
587
|
+
};
|
|
587
588
|
}
|
|
588
589
|
}
|
|
589
590
|
/**
|
|
@@ -991,7 +992,10 @@ var AgySessionManager = class AgySessionManager {
|
|
|
991
992
|
access: cached?.access ?? "",
|
|
992
993
|
expires: cached?.expires ?? 0,
|
|
993
994
|
refresh: account.refresh
|
|
994
|
-
}, {
|
|
995
|
+
}, {
|
|
996
|
+
clientId: account.clientId,
|
|
997
|
+
proxyUrl: account.proxy
|
|
998
|
+
});
|
|
995
999
|
if (result.type === "success") {
|
|
996
1000
|
if (!account.clientId && result.clientId) {
|
|
997
1001
|
await this.store.mutate((s) => {
|
|
@@ -1017,6 +1021,7 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1017
1021
|
expires: cached.expires,
|
|
1018
1022
|
refresh: account.refresh
|
|
1019
1023
|
};
|
|
1024
|
+
if (isProxyUnreachableError(result.error)) throw new AgyAuthError("transport", "proxy_unreachable", { cause: result.error });
|
|
1020
1025
|
throw new AgyAuthError(result.error.status === 429 ? "rate-limit" : result.error.status === 0 || result.error.status === 408 || result.error.status >= 500 ? "transport" : "invalid-credential", result.error.message, { cause: result.error });
|
|
1021
1026
|
}
|
|
1022
1027
|
if (result.type === "revoked") {
|
|
@@ -1076,16 +1081,19 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1076
1081
|
try {
|
|
1077
1082
|
const auth = await this.accessTokenFor(account);
|
|
1078
1083
|
if (!auth) return null;
|
|
1079
|
-
const { fetchAvailableModels } = await import("./models-
|
|
1080
|
-
const
|
|
1084
|
+
const { fetchAvailableModels } = await import("./models-DRWVi33p.mjs").then((n) => n.r);
|
|
1085
|
+
const quotas = ingestFamilyQuotas(await fetchAvailableModels(auth.access, account.projectId, (input, init) => {
|
|
1081
1086
|
const timeout = AbortSignal.timeout(AgySessionManager.QUOTA_FETCH_TIMEOUT_MS);
|
|
1082
1087
|
const signal = init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
1088
|
+
if (account.proxy) return proxiedFetch(input, {
|
|
1089
|
+
...init,
|
|
1090
|
+
signal
|
|
1091
|
+
}, { proxyUrl: account.proxy });
|
|
1083
1092
|
return fetch(input, {
|
|
1084
1093
|
...init,
|
|
1085
1094
|
signal
|
|
1086
1095
|
});
|
|
1087
|
-
};
|
|
1088
|
-
const quotas = ingestFamilyQuotas(await fetchAvailableModels(auth.access, account.projectId, boundedFetch));
|
|
1096
|
+
}));
|
|
1089
1097
|
return {
|
|
1090
1098
|
key,
|
|
1091
1099
|
quotas,
|
|
@@ -1184,11 +1192,39 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1184
1192
|
async getSession(model) {
|
|
1185
1193
|
let storage = await this.store.load();
|
|
1186
1194
|
const maxAttempts = storage.accounts.filter((account) => account.enabled !== false).length;
|
|
1195
|
+
let proxyUnreachableCount = 0;
|
|
1187
1196
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1188
1197
|
if (storage.accounts.filter((account) => account.enabled !== false).length > 1) await this.refreshQuotaCache(storage);
|
|
1189
1198
|
const picked = await this.pickAccount(storage, model);
|
|
1190
1199
|
if (!picked) return void 0;
|
|
1191
|
-
|
|
1200
|
+
let auth;
|
|
1201
|
+
try {
|
|
1202
|
+
auth = await this.accessTokenFor(picked.account);
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
if (error instanceof AgyAuthError && error.kind === "transport" && isProxyUnreachableError(error)) {
|
|
1205
|
+
proxyUnreachableCount++;
|
|
1206
|
+
this.lastUsed = null;
|
|
1207
|
+
const deadIndex = storage.accounts.findIndex((a) => this.accountKey(a) === this.accountKey(picked.account));
|
|
1208
|
+
if (deadIndex !== -1) {
|
|
1209
|
+
const next = pickNextAccountIndex(storage.accounts, deadIndex, Date.now());
|
|
1210
|
+
if (next !== storage.activeIndex) {
|
|
1211
|
+
storage.activeIndex = next;
|
|
1212
|
+
await this.store.mutate((s) => {
|
|
1213
|
+
s.activeIndex = next;
|
|
1214
|
+
}).catch(() => {});
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
storage = await this.store.load();
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
if (isProxyUnreachableError(error)) {
|
|
1221
|
+
proxyUnreachableCount++;
|
|
1222
|
+
this.lastUsed = null;
|
|
1223
|
+
storage = await this.store.load();
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
throw error;
|
|
1227
|
+
}
|
|
1192
1228
|
if (!auth) {
|
|
1193
1229
|
this.lastUsed = null;
|
|
1194
1230
|
storage = await this.store.load();
|
|
@@ -1196,7 +1232,7 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1196
1232
|
}
|
|
1197
1233
|
const key = this.accountKey(picked.account);
|
|
1198
1234
|
if (!picked.account.projectId && !this.projectRetryFailed.has(key)) try {
|
|
1199
|
-
const { loadCodeAssist } = await import("./exchange-
|
|
1235
|
+
const { loadCodeAssist } = await import("./exchange-BvrLu2E4.mjs").then((n) => n.n);
|
|
1200
1236
|
const { projectId } = await loadCodeAssist(auth.access);
|
|
1201
1237
|
if (projectId) {
|
|
1202
1238
|
await this.store.mutate((s) => {
|
|
@@ -1227,6 +1263,7 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1227
1263
|
impersonation: impersonationHeadersFor(picked.account)
|
|
1228
1264
|
};
|
|
1229
1265
|
}
|
|
1266
|
+
if (proxyUnreachableCount === maxAttempts && maxAttempts > 0) throw new AgyAuthError("transport", "proxy_unreachable");
|
|
1230
1267
|
}
|
|
1231
1268
|
/** Adapter hook: apply rotation decisions and fingerprint regeneration. */
|
|
1232
1269
|
async reportFailure(kind, session, info) {
|
|
@@ -1290,7 +1327,7 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1290
1327
|
ok: false,
|
|
1291
1328
|
error: "No agy account configured — run `dsh-agy login` first."
|
|
1292
1329
|
};
|
|
1293
|
-
const { toAgyRequestBody } = await import("./translate-
|
|
1330
|
+
const { toAgyRequestBody } = await import("./translate-COjBTH4-.mjs").then((n) => n.n);
|
|
1294
1331
|
const { fetchAgyFirstOk } = await Promise.resolve().then(() => constants_exports);
|
|
1295
1332
|
const { parseAgySse } = await import("./parse-D8bV2yqa.mjs").then((n) => n.n);
|
|
1296
1333
|
const body = toAgyRequestBody({
|
|
@@ -1370,7 +1407,7 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1370
1407
|
ok: false,
|
|
1371
1408
|
error: "refresh failed (revoked?)"
|
|
1372
1409
|
};
|
|
1373
|
-
const response = await proxiedFetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", { headers: { Authorization: `Bearer ${auth.access}` } });
|
|
1410
|
+
const response = await proxiedFetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", { headers: { Authorization: `Bearer ${auth.access}` } }, account.proxy ? { proxyUrl: account.proxy } : void 0);
|
|
1374
1411
|
if (!response.ok) return {
|
|
1375
1412
|
ok: false,
|
|
1376
1413
|
error: `userinfo ${response.status}`
|
|
@@ -1589,21 +1626,42 @@ const properFileLock = { async withLock(file, fn) {
|
|
|
1589
1626
|
function isEncrypted(value) {
|
|
1590
1627
|
return value.startsWith(ENC_PREFIX);
|
|
1591
1628
|
}
|
|
1592
|
-
/** Decrypt one account's refresh
|
|
1629
|
+
/** Decrypt one account's refresh/proxy fields when encrypted; plaintext passes through (legacy). */
|
|
1593
1630
|
function decryptAccount(account, codec) {
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1631
|
+
let out = account;
|
|
1632
|
+
if (isEncrypted(out.refresh)) out = {
|
|
1633
|
+
...out,
|
|
1634
|
+
refresh: codec.decrypt(out.refresh)
|
|
1635
|
+
};
|
|
1636
|
+
if (out.proxy && isEncrypted(out.proxy)) out = {
|
|
1637
|
+
...out,
|
|
1638
|
+
proxy: codec.decrypt(out.proxy)
|
|
1597
1639
|
};
|
|
1598
|
-
return
|
|
1640
|
+
return out;
|
|
1599
1641
|
}
|
|
1600
|
-
/** Encrypt one account's refresh
|
|
1642
|
+
/** Encrypt one account's refresh/proxy fields (plaintext stays plaintext if no codec change). */
|
|
1601
1643
|
function encryptAccount(account, codec) {
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1644
|
+
let out = account;
|
|
1645
|
+
if (!isEncrypted(out.refresh)) out = {
|
|
1646
|
+
...out,
|
|
1647
|
+
refresh: codec.encrypt(out.refresh)
|
|
1605
1648
|
};
|
|
1606
|
-
|
|
1649
|
+
if (out.proxy && !isEncrypted(out.proxy)) out = {
|
|
1650
|
+
...out,
|
|
1651
|
+
proxy: codec.encrypt(out.proxy)
|
|
1652
|
+
};
|
|
1653
|
+
return out;
|
|
1654
|
+
}
|
|
1655
|
+
/** Mask a proxy URL for display/logs: protocol//host:port (no credentials, no query). */
|
|
1656
|
+
function maskProxyUrl(proxyUrl) {
|
|
1657
|
+
if (!proxyUrl) return null;
|
|
1658
|
+
try {
|
|
1659
|
+
const u = new URL(proxyUrl);
|
|
1660
|
+
const port = u.port || (u.protocol === "https:" ? "443" : u.protocol === "socks5:" || u.protocol === "socks5h:" ? "1080" : "8080");
|
|
1661
|
+
return `${u.protocol}//${u.hostname}:${port}`;
|
|
1662
|
+
} catch {
|
|
1663
|
+
return null;
|
|
1664
|
+
}
|
|
1607
1665
|
}
|
|
1608
1666
|
function ensureAccountIds(storage) {
|
|
1609
1667
|
let mutated = false;
|
|
@@ -1761,6 +1819,6 @@ var JsonAccountStore = class {
|
|
|
1761
1819
|
}
|
|
1762
1820
|
};
|
|
1763
1821
|
//#endregion
|
|
1764
|
-
export { AgyPoolBlockedError as A,
|
|
1822
|
+
export { AgyPoolBlockedError as A, OAUTH_TOKEN_URL as C, getAgyBootstrapUserAgent as D, getAgyBootstrapClientMetadata as E, resolveAgyClientCredentials as O, OAUTH_AUTHORIZE_URL as S, fetchAgyFirstOk as T, deriveAntigravitySessionId as _, deriveKey as a, AGY_ENDPOINT_FALLBACKS as b, resolveDshHome as c, isAgyDisabled as d, generateFingerprint as f, resolveAntigravityVersionBounded as g, resolveAntigravityVersion as h, createAesGcmCodec as i, AgyAuthError as k, resolveMasterKeyCodec as l, calculateTokenExpiry as m, maskProxyUrl as n, loadMasterKey as o, recordFingerprintVersion as p, MASTER_KEY_REF as r, persistMasterKey as s, JsonAccountStore as t, AgySessionManager as u, generateAntigravityRequestId as v, OAUTH_USERINFO_URL as w, AGY_SCOPES as x, AGY_DEFAULT_REDIRECT_URI as y };
|
|
1765
1823
|
|
|
1766
|
-
//# sourceMappingURL=accounts-
|
|
1824
|
+
//# sourceMappingURL=accounts-DbTTxX_z.mjs.map
|