dsh-agy 0.2.9 → 0.2.10
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 +1 -0
- package/lib/{accounts-Um8N9I56.mjs → accounts-hsgwm7J9.mjs} +260 -233
- package/lib/accounts-hsgwm7J9.mjs.map +1 -0
- package/lib/auth-DPPVFwio.mjs +32 -0
- package/lib/auth-DPPVFwio.mjs.map +1 -0
- package/lib/cli/index.d.mts.map +1 -1
- package/lib/cli/index.mjs +8 -7
- package/lib/cli/index.mjs.map +1 -1
- package/lib/constants-BFKYjOmh.mjs +173 -0
- package/lib/constants-BFKYjOmh.mjs.map +1 -0
- package/lib/{exchange-Bdql4kf3.mjs → exchange-dQm2mjJ5.mjs} +15 -14
- package/lib/exchange-dQm2mjJ5.mjs.map +1 -0
- package/lib/identity-DsftHTJC.mjs +35 -0
- package/lib/identity-DsftHTJC.mjs.map +1 -0
- package/lib/{import-BkqCMX7m.mjs → import-D7R0GpRr.mjs} +14 -12
- package/lib/import-D7R0GpRr.mjs.map +1 -0
- package/lib/index.mjs +2 -2
- package/lib/{models-F-1cWO0f.mjs → models-IfOGIOS9.mjs} +3 -3
- package/lib/{models-F-1cWO0f.mjs.map → models-IfOGIOS9.mjs.map} +1 -1
- package/lib/{plugin-common-Di4n-1eR.mjs → plugin-common-Dk772zkm.mjs} +16 -129
- package/lib/plugin-common-Dk772zkm.mjs.map +1 -0
- package/lib/{proxy-DQPIwUov.mjs → proxy-DLvX8yxv.mjs} +63 -11
- package/lib/proxy-DLvX8yxv.mjs.map +1 -0
- package/lib/{translate-CE9tNXiV.mjs → translate-D0JHjwC-.mjs} +2 -2
- package/lib/{translate-CE9tNXiV.mjs.map → translate-D0JHjwC-.mjs.map} +1 -1
- package/lib/web/plugin.mjs +9 -9
- package/lib/web/plugin.mjs.map +1 -1
- package/package.json +3 -2
- package/lib/accounts-Um8N9I56.mjs.map +0 -1
- package/lib/exchange-Bdql4kf3.mjs.map +0 -1
- package/lib/import-BkqCMX7m.mjs.map +0 -1
- package/lib/plugin-common-Di4n-1eR.mjs.map +0 -1
- package/lib/proxy-DQPIwUov.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -230,5 +230,6 @@ pnpm run record:fixtures # re-record real-API fixtures (needs a real accou
|
|
|
230
230
|
pnpm run e2e # real-account end-to-end (needs AGY_REFRESH_TOKEN)
|
|
231
231
|
pnpm run debug:request # endpoint/header bisection probe
|
|
232
232
|
pnpm run verify:tools # live two-turn tool-signature check
|
|
233
|
+
pnpm run verify:proxy-routing # live per-account proxy routing + fail-closed check
|
|
233
234
|
npm pack --dry-run # verify the publishable artifact
|
|
234
235
|
```
|
|
@@ -1,195 +1,233 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
1
|
+
import { a as AGY_VERSION_FALLBACK, g as isProxyRouted, h as AgyPoolBlockedError, m as AgyAuthError, p as resolveAgyClientCredentials, s as OAUTH_TOKEN_URL, t as AGY_CLIENT_ID } from "./constants-BFKYjOmh.mjs";
|
|
2
|
+
import { a as proxiedFetch, r as isProxyUnreachableError, t as accountFetch } from "./proxy-DLvX8yxv.mjs";
|
|
3
|
+
import { t as deriveAntigravitySessionId } from "./identity-DsftHTJC.mjs";
|
|
4
|
+
import { i as parseRefreshParts, n as calculateTokenExpiry, r as formatRefreshParts, t as accessTokenExpired } from "./auth-DPPVFwio.mjs";
|
|
3
5
|
import { createCipheriv, createDecipheriv, createHash, randomBytes, randomUUID } from "node:crypto";
|
|
4
6
|
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
5
7
|
import { dirname, join, resolve } from "node:path";
|
|
6
8
|
import lockfile from "proper-lockfile";
|
|
7
9
|
import { Document, isMap, isScalar, parseDocument } from "yaml";
|
|
8
|
-
//#region src/
|
|
10
|
+
//#region src/runtime/classify.ts
|
|
9
11
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
+
* Upstream failure classification: HTTP status + headers + body → FailureKind.
|
|
13
|
+
* Conservative by design: only auth-failure may revoke an account; everything
|
|
14
|
+
* else cools, rotates, or retries.
|
|
12
15
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
16
|
+
const QUOTA_EXHAUSTED_KEYWORDS = [
|
|
17
|
+
"quota_exhausted",
|
|
18
|
+
"quota exhausted",
|
|
19
|
+
"quota reached",
|
|
20
|
+
"enable overages",
|
|
21
|
+
"individual quota"
|
|
22
|
+
];
|
|
23
|
+
/** Classify a 429 body into the four upstream categories. */
|
|
24
|
+
function classifyRateLimit(bodyText, retryAfterMs) {
|
|
25
|
+
const text = (bodyText ?? "").toLowerCase();
|
|
26
|
+
if (QUOTA_EXHAUSTED_KEYWORDS.some((keyword) => text.includes(keyword))) return "quota_exhausted";
|
|
27
|
+
if (retryAfterMs !== void 0 && retryAfterMs < 3e3) return "soft_rate_limit";
|
|
28
|
+
if (retryAfterMs !== void 0) return "rate_limited";
|
|
29
|
+
return text.includes("quota") || text.includes("resource_exhausted") ? "quota_exhausted" : "unknown";
|
|
30
|
+
}
|
|
31
|
+
const RATE_LIMIT_RESET_FIELDS = [
|
|
32
|
+
"resetTime",
|
|
33
|
+
"reset_time",
|
|
34
|
+
"resetAt",
|
|
35
|
+
"quotaResetTime"
|
|
36
|
+
];
|
|
37
|
+
function extractResetTime(bodyText) {
|
|
38
|
+
if (!bodyText) return void 0;
|
|
39
|
+
try {
|
|
40
|
+
const data = JSON.parse(bodyText);
|
|
41
|
+
for (const field of RATE_LIMIT_RESET_FIELDS) {
|
|
42
|
+
const value = data[field];
|
|
43
|
+
if (typeof value === "string" && value) return value;
|
|
44
|
+
if (typeof value === "number" && Number.isFinite(value)) return value > 0xe8d4a51000 ? new Date(value).toISOString() : new Date(Date.now() + value * 1e3).toISOString();
|
|
45
|
+
}
|
|
46
|
+
const quotaInfo = data.quotaInfo;
|
|
47
|
+
if (quotaInfo && typeof quotaInfo.resetTime === "string") return quotaInfo.resetTime;
|
|
48
|
+
} catch {}
|
|
49
|
+
}
|
|
50
|
+
function parseRetryAfter(header) {
|
|
51
|
+
if (!header) return void 0;
|
|
52
|
+
const seconds = Number(header);
|
|
53
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
54
|
+
const date = Date.parse(header);
|
|
55
|
+
if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
|
|
56
|
+
}
|
|
57
|
+
/** Classify a completed HTTP response (non-2xx). */
|
|
58
|
+
function classifyHttpError(status, headers, bodyText) {
|
|
59
|
+
const retryAfterMs = parseRetryAfter(headers.get("retry-after"));
|
|
60
|
+
const resetTime = extractResetTime(bodyText);
|
|
61
|
+
if (status === 429) return {
|
|
62
|
+
kind: "rate-limit",
|
|
63
|
+
rateLimitCategory: classifyRateLimit(bodyText, retryAfterMs),
|
|
64
|
+
status,
|
|
65
|
+
retryAfterMs,
|
|
66
|
+
resetTime,
|
|
67
|
+
message: bodyText ? bodyText.slice(0, 200) : void 0
|
|
68
|
+
};
|
|
69
|
+
if (status === 401) return {
|
|
70
|
+
kind: "auth-failure",
|
|
71
|
+
status,
|
|
72
|
+
message: bodyText ? bodyText.slice(0, 200) : void 0
|
|
73
|
+
};
|
|
74
|
+
if (status === 403) {
|
|
75
|
+
const category = classifyRateLimit(bodyText, void 0);
|
|
76
|
+
if (category === "quota_exhausted") return {
|
|
77
|
+
kind: "rate-limit",
|
|
78
|
+
rateLimitCategory: category,
|
|
79
|
+
status,
|
|
80
|
+
message: bodyText ? bodyText.slice(0, 200) : void 0
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
kind: "auth-failure",
|
|
84
|
+
status,
|
|
85
|
+
message: bodyText ? bodyText.slice(0, 200) : void 0
|
|
86
|
+
};
|
|
33
87
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
AGY_ENDPOINT_DAILY_SANDBOX: () => AGY_ENDPOINT_DAILY_SANDBOX,
|
|
52
|
-
AGY_ENDPOINT_FALLBACKS: () => AGY_ENDPOINT_FALLBACKS,
|
|
53
|
-
AGY_ENDPOINT_PROD: () => AGY_ENDPOINT_PROD,
|
|
54
|
-
AGY_ENDPOINT_SKIP_STATUSES: () => AGY_ENDPOINT_SKIP_STATUSES,
|
|
55
|
-
AGY_SCOPES: () => AGY_SCOPES,
|
|
56
|
-
AGY_VERSION_FALLBACK: () => AGY_VERSION_FALLBACK,
|
|
57
|
-
OAUTH_AUTHORIZE_URL: () => OAUTH_AUTHORIZE_URL,
|
|
58
|
-
OAUTH_TOKEN_URL: () => OAUTH_TOKEN_URL,
|
|
59
|
-
OAUTH_USERINFO_URL: () => OAUTH_USERINFO_URL,
|
|
60
|
-
fetchAgyFirstOk: () => fetchAgyFirstOk,
|
|
61
|
-
getAgyBootstrapClientMetadata: () => getAgyBootstrapClientMetadata,
|
|
62
|
-
getAgyBootstrapUserAgent: () => getAgyBootstrapUserAgent,
|
|
63
|
-
resolveAgyClientCredentials: () => resolveAgyClientCredentials
|
|
64
|
-
});
|
|
65
|
-
const AGY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
66
|
-
const AGY_CLIENT_SECRET = "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";
|
|
67
|
-
/**
|
|
68
|
-
* Effective OAuth client credentials: AGY_CLIENT_ID / AGY_CLIENT_SECRET env
|
|
69
|
-
* overrides win when set (BYO OAuth app escape hatch, mirrors pi-antigravity);
|
|
70
|
-
* otherwise the embedded public Antigravity credentials are used.
|
|
71
|
-
*/
|
|
72
|
-
function resolveAgyClientCredentials(overrideClientId) {
|
|
73
|
-
if (overrideClientId) {
|
|
74
|
-
if (overrideClientId === "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com") return {
|
|
75
|
-
clientId: AGY_CLIENT_ID,
|
|
76
|
-
clientSecret: AGY_CLIENT_SECRET
|
|
88
|
+
if (status === 404) return {
|
|
89
|
+
kind: "transient",
|
|
90
|
+
status,
|
|
91
|
+
message: bodyText ? bodyText.slice(0, 200) : void 0
|
|
92
|
+
};
|
|
93
|
+
if (status >= 500) return {
|
|
94
|
+
kind: "transient",
|
|
95
|
+
status,
|
|
96
|
+
retryAfterMs,
|
|
97
|
+
message: bodyText ? bodyText.slice(0, 200) : void 0
|
|
98
|
+
};
|
|
99
|
+
if (status === 400) {
|
|
100
|
+
const text = (bodyText ?? "").toLowerCase();
|
|
101
|
+
if (text.includes("context") && (text.includes("overflow") || text.includes("too long") || text.includes("exceeded")) || text.includes("model") && (text.includes("not found") || text.includes("unavailable") || text.includes("not supported"))) return {
|
|
102
|
+
kind: "transient",
|
|
103
|
+
status,
|
|
104
|
+
message: bodyText ? bodyText.slice(0, 200) : void 0
|
|
77
105
|
};
|
|
78
106
|
return {
|
|
79
|
-
|
|
80
|
-
|
|
107
|
+
kind: "request-error",
|
|
108
|
+
status,
|
|
109
|
+
message: bodyText ? bodyText.slice(0, 200) : void 0
|
|
81
110
|
};
|
|
82
111
|
}
|
|
83
112
|
return {
|
|
84
|
-
|
|
85
|
-
|
|
113
|
+
kind: "transient",
|
|
114
|
+
status,
|
|
115
|
+
message: bodyText ? bodyText.slice(0, 200) : void 0
|
|
86
116
|
};
|
|
87
117
|
}
|
|
88
|
-
/**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
118
|
+
/** Codes that carry a socket/syscall `code` worth surfacing in a failure message. */
|
|
119
|
+
const TRANSPORT_CAUSE_CODES = /* @__PURE__ */ new Set([
|
|
120
|
+
"ECONNREFUSED",
|
|
121
|
+
"ECONNRESET",
|
|
122
|
+
"ETIMEDOUT",
|
|
123
|
+
"ENETUNREACH",
|
|
124
|
+
"EHOSTUNREACH",
|
|
125
|
+
"EPIPE",
|
|
126
|
+
"ENOTFOUND",
|
|
127
|
+
"EAI_AGAIN",
|
|
128
|
+
"CERT_HAS_EXPIRED",
|
|
129
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
130
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
131
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
132
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
133
|
+
"UND_ERR_SOCKET",
|
|
134
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
135
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
136
|
+
"UND_ERR_ABORTED"
|
|
137
|
+
]);
|
|
102
138
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
139
|
+
* Strip proxy credentials from URL-like text so `user:pass` never reaches logs
|
|
140
|
+
* or the GUI. Splits at the LAST `@` of the authority, which is the real
|
|
141
|
+
* userinfo delimiter (RFC 3986: a host cannot contain a raw `@`), so a password
|
|
142
|
+
* that itself contains `@` is redacted whole rather than truncated at the first
|
|
143
|
+
* one — the failure mode of the character-class regex this replaced.
|
|
107
144
|
*/
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
AGY_ENDPOINT_PROD,
|
|
116
|
-
AGY_ENDPOINT_DAILY_SANDBOX,
|
|
117
|
-
AGY_ENDPOINT_AUTOPUSH
|
|
118
|
-
];
|
|
145
|
+
function redactCredentials(text) {
|
|
146
|
+
return text.replace(/([a-z][a-z0-9+.-]*:\/\/)([^\s/?#]+)/gi, (match, scheme, authority) => {
|
|
147
|
+
const at = authority.lastIndexOf("@");
|
|
148
|
+
if (at === -1) return match;
|
|
149
|
+
return `${scheme}<REDACTED>@${authority.slice(at + 1)}`;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
119
152
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* rejection (e.g. "No capacity available for model ..."). A 503 skipped here
|
|
123
|
-
* still reaches the caller when every endpoint fails, where the classifier marks
|
|
124
|
-
* it transient and the adapter surfaces it as a retryable SERVER error.
|
|
153
|
+
* Placeholder messages undici/Node emit while the real reason sits deeper in
|
|
154
|
+
* the cause chain; they must not shadow a usable code further down.
|
|
125
155
|
*/
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
156
|
+
const GENERIC_CAUSE_MESSAGES = /* @__PURE__ */ new Set([
|
|
157
|
+
"fetch failed",
|
|
158
|
+
"terminated",
|
|
159
|
+
"other side closed",
|
|
160
|
+
"aborted"
|
|
130
161
|
]);
|
|
131
162
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
163
|
+
* Walk the error cause chain collecting the actionable `code` and message.
|
|
164
|
+
* Node/undici reports `TypeError: fetch failed` and stores the real reason in
|
|
165
|
+
* `error.cause` (verified against live failures): without this, DNS, TLS,
|
|
166
|
+
* timeout, reset, and proxy failures are indistinguishable in DSH session
|
|
167
|
+
* events and the GUI.
|
|
137
168
|
*/
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
169
|
+
function describeCause(error) {
|
|
170
|
+
const seen = /* @__PURE__ */ new Set();
|
|
171
|
+
let current = error?.cause ?? error;
|
|
172
|
+
let fallbackMessage;
|
|
173
|
+
for (let depth = 0; current && depth < 6 && !seen.has(current); depth++) {
|
|
174
|
+
seen.add(current);
|
|
175
|
+
if (typeof current === "object") {
|
|
176
|
+
const code = current.code;
|
|
177
|
+
const message = current.message;
|
|
178
|
+
const usableMessage = typeof message === "string" && message.length > 0 ? message : void 0;
|
|
179
|
+
if (typeof code === "string" && TRANSPORT_CAUSE_CODES.has(code)) return {
|
|
180
|
+
code,
|
|
181
|
+
message: usableMessage
|
|
182
|
+
};
|
|
183
|
+
if (usableMessage && !GENERIC_CAUSE_MESSAGES.has(usableMessage) && !fallbackMessage) fallbackMessage = usableMessage;
|
|
184
|
+
const errors = current.errors;
|
|
185
|
+
if (Array.isArray(errors)) for (const nested of errors) {
|
|
186
|
+
const described = describeCause({ cause: nested });
|
|
187
|
+
if (described.code) return described;
|
|
188
|
+
if (described.message && !fallbackMessage) fallbackMessage = described.message;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
current = current?.cause;
|
|
147
192
|
}
|
|
148
|
-
|
|
149
|
-
throw new Error("all agy endpoints failed");
|
|
150
|
-
}
|
|
151
|
-
/** Default Antigravity client version used in User-Agent strings; overridden by the
|
|
152
|
-
* runtime version fetcher (see runtime/fingerprint.ts). */
|
|
153
|
-
const AGY_VERSION_FALLBACK = "1.18.3";
|
|
154
|
-
/** Electron-style UA used for bootstrap calls (loadCodeAssist/onboardUser). */
|
|
155
|
-
function getAgyBootstrapUserAgent(version = AGY_VERSION_FALLBACK) {
|
|
156
|
-
return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Antigravity/${version} Chrome/138.0.7204.235 Electron/37.3.1 Safari/537.36`;
|
|
193
|
+
return fallbackMessage ? { message: fallbackMessage } : {};
|
|
157
194
|
}
|
|
158
|
-
/** Client-Metadata payload for bootstrap calls — ideType only (backend enum
|
|
159
|
-
* validation rejects freely-added platform/pluginType; AGENTS.md invariant). */
|
|
160
|
-
function getAgyBootstrapClientMetadata() {
|
|
161
|
-
return "{\"ideType\":\"ANTIGRAVITY\"}";
|
|
162
|
-
}
|
|
163
|
-
//#endregion
|
|
164
|
-
//#region src/runtime/identity.ts
|
|
165
195
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
196
|
+
* Human-readable transport failure: `fetch failed (UND_ERR_SOCKET: other side
|
|
197
|
+
* closed)`. The sanitized cause code/message is what makes a transient socket
|
|
198
|
+
* error distinguishable from a DNS, TLS, or proxy failure in session records.
|
|
168
199
|
*/
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
function fnv1a64(input) {
|
|
177
|
-
let hash = FNV_OFFSET_I64;
|
|
178
|
-
for (let i = 0; i < input.length; i++) {
|
|
179
|
-
hash ^= BigInt(input.charCodeAt(i));
|
|
180
|
-
hash = BigInt.asIntN(64, hash * FNV_PRIME_I64);
|
|
181
|
-
}
|
|
182
|
-
return hash;
|
|
200
|
+
function describeFetchError(error) {
|
|
201
|
+
const base = error instanceof Error ? error.message : String(error);
|
|
202
|
+
const { code, message } = describeCause(error);
|
|
203
|
+
if (!code && !message) return redactCredentials(base);
|
|
204
|
+
const detail = code && message ? message.includes(code) ? message : `${code}: ${message}` : code ?? message;
|
|
205
|
+
if (detail === base) return redactCredentials(base);
|
|
206
|
+
return redactCredentials(`${base} (${detail})`);
|
|
183
207
|
}
|
|
184
208
|
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
209
|
+
* Classify a fetch-level failure (DNS, refused, timeout, abort).
|
|
210
|
+
* @param routing - how the failed request was routed; fail-closed applies only
|
|
211
|
+
* when it carried an explicit per-account proxy (see {@link AccountRouting}).
|
|
188
212
|
*/
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
213
|
+
function classifyFetchError(error, routing) {
|
|
214
|
+
const message = describeFetchError(error);
|
|
215
|
+
if (error instanceof DOMException && error.name === "AbortError") return {
|
|
216
|
+
kind: "network-error",
|
|
217
|
+
message
|
|
218
|
+
};
|
|
219
|
+
if (error?.errorCode === "proxy_unreachable" || error?.code === "PROXY_UNREACHABLE") return {
|
|
220
|
+
kind: "proxy-unreachable",
|
|
221
|
+
message
|
|
222
|
+
};
|
|
223
|
+
if (isProxyRouted(routing) && isProxyUnreachableError(error)) return {
|
|
224
|
+
kind: "proxy-unreachable",
|
|
225
|
+
message
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
kind: "network-error",
|
|
229
|
+
message
|
|
230
|
+
};
|
|
193
231
|
}
|
|
194
232
|
//#endregion
|
|
195
233
|
//#region src/runtime/version.ts
|
|
@@ -306,35 +344,6 @@ function peekCachedAntigravityVersion() {
|
|
|
306
344
|
const ideState = { inFlight: null };
|
|
307
345
|
const cliState = { inFlight: null };
|
|
308
346
|
//#endregion
|
|
309
|
-
//#region src/oauth/auth.ts
|
|
310
|
-
const ACCESS_TOKEN_EXPIRY_BUFFER_MS = 6e4;
|
|
311
|
-
/** Split the packed refresh string into its constituent refresh token and project ids. */
|
|
312
|
-
function parseRefreshParts(refresh) {
|
|
313
|
-
const [refreshToken = "", projectId = "", managedProjectId = ""] = (refresh ?? "").split("|");
|
|
314
|
-
return {
|
|
315
|
-
refreshToken: refreshToken || void 0,
|
|
316
|
-
projectId: projectId || void 0,
|
|
317
|
-
managedProjectId: managedProjectId || void 0
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
/** Serialize refresh token parts into the stored string format. */
|
|
321
|
-
function formatRefreshParts(parts) {
|
|
322
|
-
const projectSegment = parts.projectId ?? "";
|
|
323
|
-
const base = `${parts.refreshToken ?? ""}|${projectSegment}`;
|
|
324
|
-
return parts.managedProjectId ? `${base}|${parts.managedProjectId}` : base;
|
|
325
|
-
}
|
|
326
|
-
/** Whether the access token is expired or missing, with a buffer for clock skew. */
|
|
327
|
-
function accessTokenExpired(auth) {
|
|
328
|
-
if (!auth.access || typeof auth.expires !== "number") return true;
|
|
329
|
-
return auth.expires <= Date.now() + ACCESS_TOKEN_EXPIRY_BUFFER_MS;
|
|
330
|
-
}
|
|
331
|
-
/** Absolute expiry timestamp from a duration; malformed durations expire immediately. */
|
|
332
|
-
function calculateTokenExpiry(requestTimeMs, expiresInSeconds) {
|
|
333
|
-
const seconds = typeof expiresInSeconds === "number" ? expiresInSeconds : 3600;
|
|
334
|
-
if (Number.isNaN(seconds) || seconds <= 0) return requestTimeMs;
|
|
335
|
-
return requestTimeMs + seconds * 1e3;
|
|
336
|
-
}
|
|
337
|
-
//#endregion
|
|
338
347
|
//#region src/oauth/refresh.ts
|
|
339
348
|
var AgyTokenRefreshError = class extends Error {
|
|
340
349
|
code;
|
|
@@ -1033,8 +1042,9 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1033
1042
|
expires: cached.expires,
|
|
1034
1043
|
refresh: account.refresh
|
|
1035
1044
|
};
|
|
1036
|
-
if (isProxyUnreachableError(result.error)) throw new AgyAuthError("transport", "proxy_unreachable", { cause: result.error });
|
|
1037
|
-
|
|
1045
|
+
if (account.proxy && isProxyUnreachableError(result.error)) throw new AgyAuthError("transport", "proxy_unreachable", { cause: result.error });
|
|
1046
|
+
const kind = result.error.status === 429 ? "rate-limit" : result.error.status === 0 || result.error.status === 408 || result.error.status >= 500 ? "transport" : "invalid-credential";
|
|
1047
|
+
throw new AgyAuthError(kind, describeFetchError(result.error), { cause: result.error });
|
|
1038
1048
|
}
|
|
1039
1049
|
if (result.type === "revoked") {
|
|
1040
1050
|
this.tokenCache.delete(key);
|
|
@@ -1093,15 +1103,12 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1093
1103
|
try {
|
|
1094
1104
|
const auth = await this.accessTokenFor(account);
|
|
1095
1105
|
if (!auth) return null;
|
|
1096
|
-
const { fetchAvailableModels } = await import("./models-
|
|
1106
|
+
const { fetchAvailableModels } = await import("./models-IfOGIOS9.mjs").then((n) => n.r);
|
|
1107
|
+
const routed = accountFetch({ proxyUrl: account.proxy });
|
|
1097
1108
|
const quotas = ingestFamilyQuotas(await fetchAvailableModels(auth.access, account.projectId, (input, init) => {
|
|
1098
1109
|
const timeout = AbortSignal.timeout(AgySessionManager.QUOTA_FETCH_TIMEOUT_MS);
|
|
1099
1110
|
const signal = init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
1100
|
-
|
|
1101
|
-
...init,
|
|
1102
|
-
signal
|
|
1103
|
-
}, { proxyUrl: account.proxy });
|
|
1104
|
-
return fetch(input, {
|
|
1111
|
+
return routed(input, {
|
|
1105
1112
|
...init,
|
|
1106
1113
|
signal
|
|
1107
1114
|
});
|
|
@@ -1205,6 +1212,15 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1205
1212
|
let storage = await this.store.load();
|
|
1206
1213
|
const maxAttempts = storage.accounts.filter((account) => account.enabled !== false).length;
|
|
1207
1214
|
let proxyUnreachableCount = 0;
|
|
1215
|
+
/**
|
|
1216
|
+
* Last transport failure seen on a *proxyless* account. Such an account is
|
|
1217
|
+
* skipped (another enabled account may be healthy) without writing a
|
|
1218
|
+
* cooldown: a cooldown would surface as AgyPoolBlockedError — i.e. RATE_LIMIT
|
|
1219
|
+
* for a plain network error — and would block a solo pool outright. It is
|
|
1220
|
+
* remembered so the request still reports its real cause rather than
|
|
1221
|
+
* degrading into "no account configured" when every account fails this way.
|
|
1222
|
+
*/
|
|
1223
|
+
let lastTransportError;
|
|
1208
1224
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1209
1225
|
if (storage.accounts.filter((account) => account.enabled !== false).length > 1) await this.refreshQuotaCache(storage);
|
|
1210
1226
|
const picked = await this.pickAccount(storage, model);
|
|
@@ -1213,29 +1229,14 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1213
1229
|
try {
|
|
1214
1230
|
auth = await this.accessTokenFor(picked.account);
|
|
1215
1231
|
} catch (error) {
|
|
1216
|
-
if (
|
|
1232
|
+
if (Boolean(picked.account.proxy) && isProxyUnreachableError(error)) {
|
|
1217
1233
|
proxyUnreachableCount++;
|
|
1218
|
-
this.
|
|
1219
|
-
const deadIndex = storage.accounts.findIndex((a) => this.accountKey(a) === this.accountKey(picked.account));
|
|
1220
|
-
if (deadIndex !== -1) {
|
|
1221
|
-
const next = pickNextAccountIndex(storage.accounts, deadIndex, Date.now());
|
|
1222
|
-
if (next !== storage.activeIndex) {
|
|
1223
|
-
storage.activeIndex = next;
|
|
1224
|
-
await this.store.mutate((s) => {
|
|
1225
|
-
s.activeIndex = next;
|
|
1226
|
-
}).catch(() => {});
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
storage = await this.store.load();
|
|
1230
|
-
continue;
|
|
1231
|
-
}
|
|
1232
|
-
if (isProxyUnreachableError(error)) {
|
|
1233
|
-
proxyUnreachableCount++;
|
|
1234
|
-
this.lastUsed = null;
|
|
1235
|
-
storage = await this.store.load();
|
|
1234
|
+
storage = await this.skipAccount(storage, picked.account);
|
|
1236
1235
|
continue;
|
|
1237
1236
|
}
|
|
1238
|
-
|
|
1237
|
+
lastTransportError = error;
|
|
1238
|
+
storage = await this.skipAccount(storage, picked.account);
|
|
1239
|
+
continue;
|
|
1239
1240
|
}
|
|
1240
1241
|
if (!auth) {
|
|
1241
1242
|
this.lastUsed = null;
|
|
@@ -1244,8 +1245,8 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1244
1245
|
}
|
|
1245
1246
|
const key = this.accountKey(picked.account);
|
|
1246
1247
|
if (!picked.account.projectId && !this.projectRetryFailed.has(key)) try {
|
|
1247
|
-
const { loadCodeAssist } = await import("./exchange-
|
|
1248
|
-
const { projectId } = await loadCodeAssist(auth.access);
|
|
1248
|
+
const { loadCodeAssist } = await import("./exchange-dQm2mjJ5.mjs").then((n) => n.n);
|
|
1249
|
+
const { projectId } = await loadCodeAssist(auth.access, { proxyUrl: picked.account.proxy });
|
|
1249
1250
|
if (projectId) {
|
|
1250
1251
|
await this.store.mutate((s) => {
|
|
1251
1252
|
const account = s.accounts.find((candidate) => this.accountKey(candidate) === key);
|
|
@@ -1276,6 +1277,27 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1276
1277
|
};
|
|
1277
1278
|
}
|
|
1278
1279
|
if (proxyUnreachableCount === maxAttempts && maxAttempts > 0) throw new AgyAuthError("transport", "proxy_unreachable");
|
|
1280
|
+
if (lastTransportError !== void 0) throw lastTransportError;
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Drop the session pin and move the pool cursor off an account that just
|
|
1284
|
+
* failed, so the next `pickAccount` in this same request tries another one.
|
|
1285
|
+
* State on the account itself is deliberately left untouched (no cooldown).
|
|
1286
|
+
*/
|
|
1287
|
+
async skipAccount(storage, account) {
|
|
1288
|
+
this.lastUsed = null;
|
|
1289
|
+
const key = this.accountKey(account);
|
|
1290
|
+
const deadIndex = storage.accounts.findIndex((candidate) => this.accountKey(candidate) === key);
|
|
1291
|
+
if (deadIndex !== -1) {
|
|
1292
|
+
const next = pickNextAccountIndex(storage.accounts, deadIndex, Date.now());
|
|
1293
|
+
if (next !== storage.activeIndex) {
|
|
1294
|
+
storage.activeIndex = next;
|
|
1295
|
+
await this.store.mutate((s) => {
|
|
1296
|
+
s.activeIndex = next;
|
|
1297
|
+
}).catch(() => {});
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
return this.store.load();
|
|
1279
1301
|
}
|
|
1280
1302
|
/** Adapter hook: apply rotation decisions and fingerprint regeneration. */
|
|
1281
1303
|
async reportFailure(kind, session, info) {
|
|
@@ -1339,8 +1361,8 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1339
1361
|
ok: false,
|
|
1340
1362
|
error: "No agy account configured — run `dsh-agy login` first."
|
|
1341
1363
|
};
|
|
1342
|
-
const { toAgyRequestBody } = await import("./translate-
|
|
1343
|
-
const { fetchAgyFirstOk } = await
|
|
1364
|
+
const { toAgyRequestBody } = await import("./translate-D0JHjwC-.mjs").then((n) => n.n);
|
|
1365
|
+
const { fetchAgyFirstOk } = await import("./constants-BFKYjOmh.mjs").then((n) => n.l);
|
|
1344
1366
|
const { parseAgySse } = await import("./parse-Bs4rPkHA.mjs").then((n) => n.n);
|
|
1345
1367
|
const body = toAgyRequestBody({
|
|
1346
1368
|
provider: "agy",
|
|
@@ -1358,16 +1380,21 @@ var AgySessionManager = class AgySessionManager {
|
|
|
1358
1380
|
projectId: session.account.projectId,
|
|
1359
1381
|
sessionId: deriveAntigravitySessionId(session.account.email) ?? void 0
|
|
1360
1382
|
});
|
|
1383
|
+
const headers = {
|
|
1384
|
+
authorization: `Bearer ${session.auth.access}`,
|
|
1385
|
+
"content-type": "application/json",
|
|
1386
|
+
accept: "text/event-stream",
|
|
1387
|
+
...session.impersonation
|
|
1388
|
+
};
|
|
1389
|
+
const routing = {
|
|
1390
|
+
proxyUrl: session.account.proxy,
|
|
1391
|
+
streaming: true
|
|
1392
|
+
};
|
|
1361
1393
|
const response = await fetchAgyFirstOk("/v1internal:streamGenerateContent?alt=sse", {
|
|
1362
1394
|
method: "POST",
|
|
1363
|
-
headers
|
|
1364
|
-
authorization: `Bearer ${session.auth.access}`,
|
|
1365
|
-
"content-type": "application/json",
|
|
1366
|
-
accept: "text/event-stream",
|
|
1367
|
-
...session.impersonation
|
|
1368
|
-
},
|
|
1395
|
+
headers,
|
|
1369
1396
|
body: JSON.stringify(body)
|
|
1370
|
-
});
|
|
1397
|
+
}, accountFetch(routing), routing);
|
|
1371
1398
|
if (!response.ok) {
|
|
1372
1399
|
const text = await response.text().catch(() => "");
|
|
1373
1400
|
return {
|
|
@@ -1945,6 +1972,6 @@ var JsonAccountStore = class {
|
|
|
1945
1972
|
}
|
|
1946
1973
|
};
|
|
1947
1974
|
//#endregion
|
|
1948
|
-
export {
|
|
1975
|
+
export { classifyHttpError as _, deriveKey as a, resolveDshHome as c, isAgyDisabled as d, generateFingerprint as f, classifyFetchError as g, resolveAntigravityVersionBounded as h, createAesGcmCodec as i, resolveMasterKeyCodec as l, resolveAntigravityVersion 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, describeFetchError as v };
|
|
1949
1976
|
|
|
1950
|
-
//# sourceMappingURL=accounts-
|
|
1977
|
+
//# sourceMappingURL=accounts-hsgwm7J9.mjs.map
|