dsh-agy 0.4.5 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +8 -3
  2. package/lib/{accounts-BOuz6_Sh.mjs → accounts-DtIsWCeF.mjs} +15 -11
  3. package/lib/{accounts-BOuz6_Sh.mjs.map → accounts-DtIsWCeF.mjs.map} +1 -1
  4. package/lib/cli/index.mjs +6 -6
  5. package/lib/client.js +181 -23
  6. package/lib/{constants-CmA9wXNe.mjs → constants-AdsD4sKh.mjs} +3 -3
  7. package/lib/{constants-CmA9wXNe.mjs.map → constants-AdsD4sKh.mjs.map} +1 -1
  8. package/lib/{exchange-C-tndv7j.mjs → exchange-B_rHhU8K.mjs} +3 -3
  9. package/lib/{exchange-C-tndv7j.mjs.map → exchange-B_rHhU8K.mjs.map} +1 -1
  10. package/lib/{import-SxPDjH3u.mjs → import-BqyA9tzt.mjs} +4 -4
  11. package/lib/{import-SxPDjH3u.mjs.map → import-BqyA9tzt.mjs.map} +1 -1
  12. package/lib/index.mjs +2 -2
  13. package/lib/{models-CjB0rXtI.mjs → models-D_54Ro5z.mjs} +3 -3
  14. package/lib/{models-CjB0rXtI.mjs.map → models-D_54Ro5z.mjs.map} +1 -1
  15. package/lib/{parse-D3InNyUx.mjs → parse-CeJc7cHo.mjs} +108 -8
  16. package/lib/parse-CeJc7cHo.mjs.map +1 -0
  17. package/lib/{plugin-common-Dwa1K13O.mjs → plugin-common-BlGbNHxX.mjs} +267 -10
  18. package/lib/plugin-common-BlGbNHxX.mjs.map +1 -0
  19. package/lib/{proxy-BbuQX5U9.mjs → proxy-B7VnbAfO.mjs} +5 -2
  20. package/lib/proxy-B7VnbAfO.mjs.map +1 -0
  21. package/lib/{quota-summary-DEA3P3Zh.mjs → quota-summary-DarIZkmo.mjs} +3 -3
  22. package/lib/{quota-summary-DEA3P3Zh.mjs.map → quota-summary-DarIZkmo.mjs.map} +1 -1
  23. package/lib/{translate-Cq6PGO4b.mjs → translate-vh3M5H_9.mjs} +7 -6
  24. package/lib/translate-vh3M5H_9.mjs.map +1 -0
  25. package/lib/web/plugin.d.mts +23 -1
  26. package/lib/web/plugin.d.mts.map +1 -1
  27. package/lib/web/plugin.mjs +86 -19
  28. package/lib/web/plugin.mjs.map +1 -1
  29. package/package.json +1 -1
  30. package/lib/parse-D3InNyUx.mjs.map +0 -1
  31. package/lib/plugin-common-Dwa1K13O.mjs.map +0 -1
  32. package/lib/proxy-BbuQX5U9.mjs.map +0 -1
  33. package/lib/translate-Cq6PGO4b.mjs.map +0 -1
package/lib/cli/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { n as AGY_DEFAULT_REDIRECT_URI } from "../constants-CmA9wXNe.mjs";
2
- import { i as normalizeProxyUrl, n as isProxyReachable, o as proxiedFetch, t as accountFetch } from "../proxy-BbuQX5U9.mjs";
3
- import { c as resolveAntigravityVersionBounded, f as migrateToAgyDir, g as loadMasterKey, h as deriveKey, i as isAgyDisabled, m as createAesGcmCodec, n as maskProxyUrl, r as AgySessionManager, t as JsonAccountStore, v as resolveDshHome, y as resolveMasterKeyCodec } from "../accounts-BOuz6_Sh.mjs";
4
- import { t as exchangeAntigravity } from "../exchange-C-tndv7j.mjs";
5
- import { n as upsertImportedAccount, r as authorizeAntigravity, t as importManySources } from "../import-SxPDjH3u.mjs";
1
+ import { n as AGY_DEFAULT_REDIRECT_URI } from "../constants-AdsD4sKh.mjs";
2
+ import { i as normalizeProxyUrl, n as isProxyReachable, o as proxiedFetch, t as accountFetch } from "../proxy-B7VnbAfO.mjs";
3
+ import { c as resolveAntigravityVersionBounded, f as migrateToAgyDir, g as loadMasterKey, h as deriveKey, i as isAgyDisabled, m as createAesGcmCodec, n as maskProxyUrl, r as AgySessionManager, t as JsonAccountStore, v as resolveDshHome, y as resolveMasterKeyCodec } from "../accounts-DtIsWCeF.mjs";
4
+ import { t as exchangeAntigravity } from "../exchange-B_rHhU8K.mjs";
5
+ import { n as upsertImportedAccount, r as authorizeAntigravity, t as importManySources } from "../import-BqyA9tzt.mjs";
6
6
  import { r as encodeCredentialBlob } from "../blob-D1e7_uT1.mjs";
7
7
  import { readFileSync, writeFileSync } from "node:fs";
8
8
  import { join } from "node:path";
@@ -256,7 +256,7 @@ async function statusCommand() {
256
256
  console.log(` ${marker} [${index}] ${account.email ?? "(no email)"} — ${state}${account.projectId ? ` (project: ${account.projectId})` : ""}${maskedProxy ? ` proxy: ${maskedProxy}` : ""}`);
257
257
  const session = await sessions.getSession().catch(() => void 0);
258
258
  if (session && session.index === index) try {
259
- const { fetchAvailableModels } = await import("../models-CjB0rXtI.mjs").then((n) => n.r);
259
+ const { fetchAvailableModels } = await import("../models-D_54Ro5z.mjs").then((n) => n.r);
260
260
  const discovered = await fetchAvailableModels(session.auth.access, session.account.projectId, accountFetch({ proxyUrl: session.account.proxy }));
261
261
  const entries = Object.entries(discovered.models ?? {});
262
262
  if (entries.length > 0) {
package/lib/client.js CHANGED
@@ -321,6 +321,11 @@ window.__ModuleLoader__.load({
321
321
  align-items: center; gap: 14px; padding: 8px 0;
322
322
  }
323
323
  .agy-thinking-k { font: var(--dsw-font-xxs-12); color: ${aliasVar("label-secondary")}; }
324
+ /* The unit sits INSIDE the third column, left of the chips: that auto column is
325
+ what holds "what the number counts" next to the field it counts, and a unit
326
+ drifting to the input's far edge would read as a separate label. */
327
+ .agy-input-unit { display: flex; align-items: center; gap: 8px;
328
+ font: var(--dsw-font-xxs-12); color: ${aliasVar("label-tertiary")}; }
324
329
  /* Shortcut chips, not a second control: they fill the field beside them. */
325
330
  .agy-thinking-chips { display: flex; gap: 6px; }
326
331
  .agy-thinking-chip {
@@ -931,6 +936,7 @@ window.__ModuleLoader__.load({
931
936
  loading: "加载中…",
932
937
  tabAccounts: "账号",
933
938
  tabModels: "模型",
939
+ tabConfig: "配置",
934
940
  tabUsage: "用量",
935
941
  tabCredentials: "凭据",
936
942
  stateActive: "可用",
@@ -1012,6 +1018,7 @@ window.__ModuleLoader__.load({
1012
1018
  cooldownReasonQuotaExhausted: "额度耗尽",
1013
1019
  cooldownReasonValidationRequired: "需要验证",
1014
1020
  cooldownReasonProjectError: "项目错误",
1021
+ reasonAborted: "已取消",
1015
1022
  fieldSources: "调用来源",
1016
1023
  fieldLatency: "延迟",
1017
1024
  fieldVerification: "验证",
@@ -1124,6 +1131,17 @@ window.__ModuleLoader__.load({
1124
1131
  thinkingClaudeMaxTokens: "必须小于单次回复上限,否则不发送。",
1125
1132
  thinkingClaudeNoReport: "上游不回报实际思考量,故无参考表。",
1126
1133
  thinkingInvalid: "请填整数。",
1134
+ multimodalTitle: "多模态内联上限",
1135
+ multimodalLabel: "单文件上限",
1136
+ multimodalUnit: "MB",
1137
+ multimodalAuto: "默认",
1138
+ multimodalClear: "清除",
1139
+ multimodalHint: "超过上限的文件不会被内联发送,但它的文件句柄仍留在对话里,模型依然可以用文件工具读取。",
1140
+ multimodalInvalid: "请填整数。",
1141
+ multimodalRange: "请填 {min}–{max} 之间的整数。",
1142
+ multimodalSourceDefault: "未设置 · 默认 {default} MB",
1143
+ multimodalSourceStored: "已保存的设置生效中",
1144
+ multimodalSourceEnv: "环境变量 DSH_AGY_MULTIMODAL_MAX_INLINE_MB 优先于此处设置。",
1127
1145
  modelsTitle: "模型可见性",
1128
1146
  modelsHelp: "关闭开关后,该模型不再出现在对话框的模型选择里。黑名单制:只有被关闭的才隐藏,服务端新增的模型一律默认显示。",
1129
1147
  modelsHiddenSuffix: "当前已隐藏 {count} 个。",
@@ -1144,6 +1162,7 @@ window.__ModuleLoader__.load({
1144
1162
  loading: "Loading…",
1145
1163
  tabAccounts: "Accounts",
1146
1164
  tabModels: "Models",
1165
+ tabConfig: "Config",
1147
1166
  tabUsage: "Usage",
1148
1167
  tabCredentials: "Credentials",
1149
1168
  stateActive: "Ready",
@@ -1225,6 +1244,7 @@ window.__ModuleLoader__.load({
1225
1244
  cooldownReasonQuotaExhausted: "quota exhausted",
1226
1245
  cooldownReasonValidationRequired: "verification required",
1227
1246
  cooldownReasonProjectError: "project error",
1247
+ reasonAborted: "cancelled",
1228
1248
  fieldSources: "Call sources",
1229
1249
  fieldLatency: "Latency",
1230
1250
  fieldVerification: "Verification",
@@ -1337,6 +1357,17 @@ window.__ModuleLoader__.load({
1337
1357
  thinkingClaudeMaxTokens: "Must be below the reply limit, or it is not sent.",
1338
1358
  thinkingClaudeNoReport: "Upstream does not report thinking tokens, so there is no reference table.",
1339
1359
  thinkingInvalid: "Enter a whole number.",
1360
+ multimodalTitle: "Multimodal inline limit",
1361
+ multimodalLabel: "Per file",
1362
+ multimodalUnit: "MB",
1363
+ multimodalAuto: "default",
1364
+ multimodalClear: "Clear",
1365
+ multimodalHint: "A file over the limit is not inlined, but its file handle stays in the conversation and the model can still read it with its file tools.",
1366
+ multimodalInvalid: "Enter a whole number.",
1367
+ multimodalRange: "Enter a whole number between {min} and {max}.",
1368
+ multimodalSourceDefault: "Unset · default {default} MB",
1369
+ multimodalSourceStored: "Your saved setting is in force",
1370
+ multimodalSourceEnv: "The DSH_AGY_MULTIMODAL_MAX_INLINE_MB environment variable overrides the setting here.",
1340
1371
  modelsTitle: "Model visibility",
1341
1372
  modelsHelp: "Switching a model off removes it from the model picker. Blacklist semantics: only models you switch off are hidden, so models the server adds later stay visible.",
1342
1373
  modelsHiddenSuffix: "{count} hidden right now.",
@@ -2116,7 +2147,7 @@ window.__ModuleLoader__.load({
2116
2147
  /**
2117
2148
  * agy Settings section — the browser half.
2118
2149
  *
2119
- * One `settings.section` page with four tabs (accounts, models, usage,
2150
+ * One `settings.section` page with five tabs (accounts, models, config, usage,
2120
2151
  * credentials), replacing the standalone `/agy` dashboard. Every figure comes
2121
2152
  * from the `/api/agy` RPC over `ctx.connection`, so this section needs no
2122
2153
  * host-rendered page and there is no second UI to keep in step.
@@ -2731,7 +2762,7 @@ window.__ModuleLoader__.load({
2731
2762
  return [...models].sort((a, b) => Number(a.disabled) - Number(b.disabled));
2732
2763
  }
2733
2764
  function ModelsTab(props) {
2734
- const { models, account, pending, testing, onToggle, onTestModel, rpc, t } = props;
2765
+ const { models, account, pending, testing, onToggle, onTestModel, t } = props;
2735
2766
  const ordered = (0, react.useMemo)(() => orderModels(models), [models]);
2736
2767
  if (models.length === 0) return card(t("modelsTitle"), h("div", { className: "agy-empty" }, t("emptyModels")));
2737
2768
  const hidden = models.filter((model) => model.disabled).length;
@@ -2752,26 +2783,9 @@ window.__ModuleLoader__.load({
2752
2783
  onToggle(model.id, !model.disabled);
2753
2784
  }
2754
2785
  }))));
2755
- return h("div", { className: "agy-root" }, card(t("modelsTitle"), h("div", { className: "agy-rows" }, ...rows), hidden > 0 ? t("modelsHiddenSuffix", { count: hidden }) : account ?? void 0), hint(t("modelsHelp")), h(ThinkingBudgetCard, {
2756
- rpc,
2757
- t
2758
- }));
2786
+ return h("div", { className: "agy-root" }, card(t("modelsTitle"), h("div", { className: "agy-rows" }, ...rows), hidden > 0 ? t("modelsHiddenSuffix", { count: hidden }) : account ?? void 0), hint(t("modelsHelp")));
2759
2787
  }
2760
2788
  /**
2761
- * The global reasoning-level token budgets.
2762
- *
2763
- * One row per level rather than per model: only `*-tiered` models send a
2764
- * `thinkingConfig` at all, and the level itself is already chosen in DSH's model
2765
- * selector. So this supplies the missing VALUE behind each level — the same three
2766
- * numbers for every such model.
2767
- *
2768
- * An EMPTY input is the meaningful default: the request then sends
2769
- * `thinkingLevel` and lets upstream pick, which is exactly the behaviour before
2770
- * this setting existed. That is why the field is not a `number` input with a
2771
- * zero fallback, and why clearing it is a real action rather than "set to 0"
2772
- * (measured: `0` reduces thinking but does not reliably disable it).
2773
- */
2774
- /**
2775
2789
  * One budget row: label, input, and optional shortcut chips.
2776
2790
  *
2777
2791
  * There is deliberately NO trailing text column. An earlier version put the wire
@@ -2902,6 +2916,20 @@ window.__ModuleLoader__.load({
2902
2916
  }
2903
2917
  }, h("span", { className: "agy-caret" }), h("span", null, t("thinkingSamplesTitle"))), show ? h("div", { className: "agy-disclosure-body" }, h("p", { className: "agy-hint" }, t("thinkingSamplesIntro")), h("p", { className: "agy-hint" }, t("thinkingSamplesCaption")), h("div", { className: "agy-table-wrap" }, table(h("tr", null, h("th", null, t("thinkingSamplesLevel")), h("th", { className: "agy-num" }, t("thinkingSamplesEasy")), h("th", { className: "agy-num" }, t("thinkingSamplesMedium")), h("th", { className: "agy-num" }, t("thinkingSamplesHard"))), THINKING_SAMPLES.map((row) => h("tr", { key: row.level }, h("td", { className: "agy-strong" }, row.level === "max" ? t("thinkingSamplesMaxRow") : row.level), cell(row.easy), cell(row.medium), cell(row.hard))))), h("p", { className: "agy-hint agy-table-note" }, t("thinkingSamplesComparison")), h("p", { className: "agy-hint agy-table-note" }, t("thinkingSamplesSources")), h("p", { className: "agy-hint agy-table-note" }, t("thinkingSamplesZero"))) : null);
2904
2918
  }
2919
+ /**
2920
+ * The global reasoning-level token budgets, on the config tab.
2921
+ *
2922
+ * One row per level rather than per model: only `*-tiered` models send a
2923
+ * `thinkingConfig` at all, and the level itself is already chosen in DSH's model
2924
+ * selector. So this supplies the missing VALUE behind each level — the same three
2925
+ * numbers for every such model.
2926
+ *
2927
+ * An EMPTY input is the meaningful default: the request then sends
2928
+ * `thinkingLevel` and lets upstream pick, which is exactly the behaviour before
2929
+ * this setting existed. That is why the field is not a `number` input with a
2930
+ * zero fallback, and why clearing it is a real action rather than "set to 0"
2931
+ * (measured: `0` reduces thinking but does not reliably disable it).
2932
+ */
2905
2933
  function ThinkingBudgetCard(props) {
2906
2934
  const { rpc, t } = props;
2907
2935
  const [budgets, setBudgets] = (0, react.useState)({});
@@ -3064,6 +3092,129 @@ window.__ModuleLoader__.load({
3064
3092
  }))) : null);
3065
3093
  return card(t("thinkingTitle"), block);
3066
3094
  }
3095
+ /**
3096
+ * The config tab: a card container, not one component.
3097
+ *
3098
+ * Each card loads and saves its OWN state (the thinking card already had that
3099
+ * shape), so a new knob is a new card rather than a field threaded through a
3100
+ * shared form — and one card's failed load cannot blank the others. Nothing here
3101
+ * holds cross-card state, which is the property that keeps that true.
3102
+ */
3103
+ function ConfigTab(props) {
3104
+ const { rpc, t } = props;
3105
+ return h("div", { className: "agy-root" }, h(ThinkingBudgetCard, {
3106
+ rpc,
3107
+ t
3108
+ }), h(MultimodalCard, {
3109
+ rpc,
3110
+ t
3111
+ }));
3112
+ }
3113
+ /**
3114
+ * The per-file inline cap for non-image multimodal files.
3115
+ *
3116
+ * A plain card, not a disclosure like the thinking block: it is one number, so
3117
+ * there is nothing to collapse, and the same self-loading shape as the thinking
3118
+ * card (load on mount, draft mirrored as a string, blur commits).
3119
+ *
3120
+ * The EMPTY semantics are inherited deliberately: an empty box means "no stored
3121
+ * setting", which falls back to the env override or the 20MB default. That is
3122
+ * why clearing has its own button rather than being reachable only by emptying
3123
+ * the field — the destructive direction should be an explicit click, not the
3124
+ * side effect of a blur on a field the user emptied while deciding.
3125
+ */
3126
+ function MultimodalCard(props) {
3127
+ const { rpc, t } = props;
3128
+ const [view, setView] = (0, react.useState)(null);
3129
+ const [draft, setDraft] = (0, react.useState)("");
3130
+ const [error, setError] = (0, react.useState)(void 0);
3131
+ const [loaded, setLoaded] = (0, react.useState)(false);
3132
+ const alive = (0, react.useRef)(true);
3133
+ (0, react.useEffect)(() => {
3134
+ alive.current = true;
3135
+ return () => {
3136
+ alive.current = false;
3137
+ };
3138
+ }, []);
3139
+ const load = (0, react.useCallback)(() => {
3140
+ (async () => {
3141
+ try {
3142
+ const result = await rpc.call("multimodal.get", {});
3143
+ if (!alive.current) return;
3144
+ setView(result);
3145
+ setDraft(result.value === null ? "" : String(result.value));
3146
+ setError(void 0);
3147
+ } catch (caught) {
3148
+ if (!alive.current) return;
3149
+ setError(caught instanceof Error ? caught.message : String(caught));
3150
+ } finally {
3151
+ if (alive.current) setLoaded(true);
3152
+ }
3153
+ })();
3154
+ }, [rpc]);
3155
+ (0, react.useEffect)(() => {
3156
+ load();
3157
+ }, [load]);
3158
+ const commit = (value) => {
3159
+ (async () => {
3160
+ try {
3161
+ const result = await rpc.call("multimodal.set", { maxInlineMb: value });
3162
+ if (!alive.current) return;
3163
+ setView(result);
3164
+ setDraft(result.value === null ? "" : String(result.value));
3165
+ setError(void 0);
3166
+ } catch (caught) {
3167
+ if (!alive.current) return;
3168
+ setError(caught instanceof Error ? caught.message : String(caught));
3169
+ }
3170
+ })();
3171
+ };
3172
+ const save = (raw) => {
3173
+ const trimmed = raw.trim();
3174
+ if (trimmed === "") {
3175
+ commit(null);
3176
+ return;
3177
+ }
3178
+ const value = Number(trimmed);
3179
+ if (!Number.isInteger(value)) {
3180
+ setError(t("multimodalInvalid"));
3181
+ return;
3182
+ }
3183
+ if (value < 1 || value > 100) {
3184
+ setError(t("multimodalRange", {
3185
+ min: 1,
3186
+ max: 100
3187
+ }));
3188
+ return;
3189
+ }
3190
+ commit(value);
3191
+ };
3192
+ const source = view === null ? null : view.source === "env" ? t("multimodalSourceEnv") : view.source === "stored" ? t("multimodalSourceStored") : t("multimodalSourceDefault", { default: 20 });
3193
+ return card(t("multimodalTitle"), [
3194
+ h("div", { className: "agy-thinking-row" }, h("span", { className: "agy-thinking-k" }, t("multimodalLabel")), h(_deepseek_ai_dsh_client_ui_primitives.Input, {
3195
+ value: draft,
3196
+ placeholder: t("multimodalAuto"),
3197
+ inputMode: "numeric",
3198
+ disabled: !loaded,
3199
+ onChange: (event) => {
3200
+ setDraft(event.target.value);
3201
+ },
3202
+ onBlur: (event) => {
3203
+ save(event.target.value);
3204
+ }
3205
+ }), h("span", { className: "agy-input-unit" }, t("multimodalUnit"), h("button", {
3206
+ type: "button",
3207
+ className: "agy-thinking-chip",
3208
+ onClick: () => {
3209
+ setDraft("");
3210
+ commit(null);
3211
+ }
3212
+ }, t("multimodalClear")))),
3213
+ source === null ? null : h("div", { className: "agy-hint" }, source),
3214
+ hint(t("multimodalHint")),
3215
+ error === void 0 ? null : h("div", { className: "agy-error" }, error)
3216
+ ]);
3217
+ }
3067
3218
  /** Result vocabulary for one recent row; rotation events are their own kind. */
3068
3219
  function recentResultKind(entry) {
3069
3220
  if (entry.kind === "rotation") return "rotation";
@@ -3074,6 +3225,10 @@ window.__ModuleLoader__.load({
3074
3225
  /**
3075
3226
  * Localized label for a failure-classification token, falling back to the raw
3076
3227
  * token so a classification added upstream still reads as something.
3228
+ *
3229
+ * 'aborted' is the one token that is NOT an upstream classification (it marks a
3230
+ * turn the consumer cancelled), and it is listed here so the ring reads
3231
+ * "cancelled" rather than leaking the raw ledger token.
3077
3232
  */
3078
3233
  function failureReasonLabel(reason, t) {
3079
3234
  switch (reason) {
@@ -3083,6 +3238,7 @@ window.__ModuleLoader__.load({
3083
3238
  case "verification-required": return t("cooldownReasonValidationRequired");
3084
3239
  case "quota-exhausted": return t("cooldownReasonQuotaExhausted");
3085
3240
  case "project-error": return t("cooldownReasonProjectError");
3241
+ case "aborted": return t("reasonAborted");
3086
3242
  default: return reason;
3087
3243
  }
3088
3244
  }
@@ -3703,7 +3859,6 @@ window.__ModuleLoader__.load({
3703
3859
  account: modelAccount,
3704
3860
  pending: toggling,
3705
3861
  testing: modelTesting,
3706
- rpc,
3707
3862
  t,
3708
3863
  /**
3709
3864
  * Optimistic, per-model toggle.
@@ -3771,7 +3926,10 @@ window.__ModuleLoader__.load({
3771
3926
  }
3772
3927
  }) : h("div", { className: "agy-root" }, h("div", { className: "agy-error" }, modelError), button(t("refresh"), () => {
3773
3928
  loadModels();
3774
- }, { size: "sm" })) : tab === "usage" ? h(UsageTab, {
3929
+ }, { size: "sm" })) : tab === "config" ? h(ConfigTab, {
3930
+ rpc,
3931
+ t
3932
+ }) : tab === "usage" ? h(UsageTab, {
3775
3933
  stats,
3776
3934
  lang: props.lang,
3777
3935
  t
@@ -3808,7 +3966,7 @@ window.__ModuleLoader__.load({
3808
3966
  }), button(t("login"), startLogin, {
3809
3967
  size: "sm",
3810
3968
  disabled: busy
3811
- }))), h("div", { className: "agy-tabs" }, tabButton("accounts", t("tabAccounts"), accounts.length), tabButton("models", t("tabModels"), models.length > 0 ? models.length : void 0), tabButton("usage", t("tabUsage")), tabButton("credentials", t("tabCredentials"))), error === void 0 ? null : h("div", { className: "agy-error" }, error), actionError === void 0 ? null : h("div", { className: "agy-error" }, actionError), notice === void 0 ? null : h("div", { className: "agy-notice" }, notice), body, loaded || error !== void 0 ? null : h("div", { className: "agy-empty" }, t("loading")));
3969
+ }))), h("div", { className: "agy-tabs" }, tabButton("accounts", t("tabAccounts"), accounts.length), tabButton("models", t("tabModels"), models.length > 0 ? models.length : void 0), tabButton("config", t("tabConfig")), tabButton("usage", t("tabUsage")), tabButton("credentials", t("tabCredentials"))), error === void 0 ? null : h("div", { className: "agy-error" }, error), actionError === void 0 ? null : h("div", { className: "agy-error" }, actionError), notice === void 0 ? null : h("div", { className: "agy-notice" }, notice), body, loaded || error !== void 0 ? null : h("div", { className: "agy-empty" }, t("loading")));
3812
3970
  }
3813
3971
  /**
3814
3972
  * Register the Antigravity Settings section while this client plugin is active.
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { o as proxiedFetch } from "./proxy-BbuQX5U9.mjs";
2
+ import { o as proxiedFetch } from "./proxy-B7VnbAfO.mjs";
3
3
  //#region src/types.ts
4
4
  /**
5
5
  * The upstream `window` tokens we understand: their ORDER by duration, and which
@@ -187,7 +187,7 @@ async function fetchAgyFirstOk(urlPath, init, fetchImpl = proxiedFetch, options
187
187
  } catch (error) {
188
188
  lastNetworkError = error;
189
189
  if (isProxyRouted(options)) {
190
- const { isProxyUnreachableError } = await import("./proxy-BbuQX5U9.mjs").then((n) => n.c);
190
+ const { isProxyUnreachableError } = await import("./proxy-B7VnbAfO.mjs").then((n) => n.c);
191
191
  if (isProxyUnreachableError(error)) throw error;
192
192
  }
193
193
  }
@@ -271,4 +271,4 @@ const AGY_IDE_TYPE = "ANTIGRAVITY";
271
271
  //#endregion
272
272
  export { setResolvedAgyVersion as _, AGY_PLATFORM_ENUM as a, QUOTA_WINDOWS as b, OAUTH_AUTHORIZE_URL as c, antigravityUserAgent as d, constants_exports as f, resolveAgyClientCredentials as g, getAgyBootstrapUserAgent as h, AGY_IDE_TYPE as i, OAUTH_TOKEN_URL as l, fetchAgyFirstOk as m, AGY_DEFAULT_REDIRECT_URI as n, AGY_SCOPES as o, currentAgyVersion as p, AGY_ENDPOINT_FALLBACKS as r, AGY_VERSION_FALLBACK as s, AGY_CLIENT_ID as t, OAUTH_USERINFO_URL as u, AgyAuthError as v, isProxyRouted as x, AgyPoolBlockedError as y };
273
273
 
274
- //# sourceMappingURL=constants-CmA9wXNe.mjs.map
274
+ //# sourceMappingURL=constants-AdsD4sKh.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants-CmA9wXNe.mjs","names":[],"sources":["../src/types.ts","../src/oauth/constants.ts"],"sourcesContent":["/** Shared domain types for dsh-agy. */\n\n/**\n * Device fingerprint persisted per account (rate-limit mitigation).\n *\n * Mirrors `google.internal.cloud.code.v1internal.ClientMetadata`, read out of the\n * installed official CLI's own descriptor (`docs/official-identity.json`). The\n * message has eight fields; only the three below are populated, because the rest\n * (`pluginVersion`, `updateChannel`, `duetProject`, `pluginType`, `ideName`) have\n * no captured value and a wrong value is a worse anomaly than an absent one.\n *\n * This is the BODY message (`metadata`), not a header. Nothing here is sent as a\n * `Client-Metadata` header: neither official binary contains that header name.\n */\nexport interface ClientMetadata {\n ideType: string\n /** Client version for the claimed product line (the CLI's, not the IDE's). */\n ideVersion?: string\n /**\n * `ClientMetadata.platform` enum NAME (`DARWIN_ARM64`), which is a DIFFERENT\n * vocabulary from the UA's `darwin/arm64` token — both exist, and conflating\n * them is how `\"MACOS\"` came to be sent and rejected.\n */\n platform?: string\n}\n\nexport interface Fingerprint {\n deviceId: string\n sessionToken: string\n userAgent: string\n apiClient: string\n clientMetadata: ClientMetadata\n createdAt: number\n}\n\nexport interface FingerprintVersion {\n fingerprint: Fingerprint\n timestamp: number\n reason: 'initial' | 'regenerated' | 'restored'\n}\n\nexport type CooldownReason =\n | 'auth-failure'\n | 'network-error'\n | 'project-error'\n | 'quota-exhausted'\n | 'validation-required'\n\n/**\n * Per-account quota cache keyed by model FAMILY (`google` / `anthropic` /\n * `openai`, or `unknown`) — never by model id. See `familyKeyOf` in\n * `runtime/quota.ts`.\n *\n * The keying is load-bearing for the fields below: a family has exactly ONE\n * record, so a family-scoped reading stored here cannot disagree with a\n * per-model copy of itself, and there is no second key to keep in sync.\n */\nexport interface CachedQuota {\n /** 0..1 left in the family's rolling 5-hour window. */\n remainingFraction?: number\n /** When the 5-hour window refills (RFC3339). */\n resetTime?: string\n /**\n * 0..1 left in the family's 7-day window, when `retrieveUserQuotaSummary`\n * reported one.\n *\n * A genuinely SEPARATE window, not a second view of `remainingFraction`: the\n * 5-hour bucket refills four times a day while the weekly budget only drains,\n * so an account can be comfortable on one and exhausted on the other.\n *\n * It always travels with its own `weeklyResetTime`, and that timestamp is what\n * bounds the value's life: once it passes, every consumer ignores the reading\n * (`isFamilyDrained`, `parseFutureResetMs`, `rankPoolCandidates`), so a weekly\n * value carried forward across a failed probe cannot outlive its window.\n */\n weeklyFraction?: number\n /** When the 7-day window refills (RFC3339). */\n weeklyResetTime?: string\n /** How many models the per-model probe contributed to this family's reading. */\n modelCount?: number\n}\n\n/**\n * The upstream `window` tokens we understand: their ORDER by duration, and which\n * of them `CachedQuota` has a field for.\n *\n * ONE table for a vocabulary that used to live in two places giving two different\n * answers: `adapter/quota-summary.ts` sorted by its own token table while\n * `runtime/quota.ts` classified windows with `includes()` checks. The\n * disagreement was not academic — `daily` existed in one and not the other, so a\n * daily group parsed and display-sorted correctly, then contributed an EMPTY\n * record that replaced a real measurement.\n *\n * Keeping both facts in one row is deliberate: a token cannot be rankable in one\n * consumer and unknown to the other, because there is only one place to add it.\n * A missing `kind` means the cache has nowhere to put that reading, which is what\n * stops the empty-record overwrite.\n *\n * Declared in this leaf rather than in either consumer because BOTH need it:\n * `runtime/` must not reach into `adapter/`, and the persisted `QuotaWindow` is\n * already defined here.\n *\n * `rank` is explicit rather than inferred from token length so it states the\n * intended duration ordering: `daily` (5 chars) would otherwise sort before\n * `weekly` (6) for the wrong reason.\n */\nexport const QUOTA_WINDOWS: Record<string, { rank: number, kind?: 'rolling' | 'weekly' }> = {\n '5h': { rank: 0, kind: 'rolling' },\n daily: { rank: 1 },\n weekly: { rank: 2, kind: 'weekly' },\n monthly: { rank: 3 },\n}\n\n/**\n * One window of one `QuotaGroup`, as `retrieveUserQuotaSummary` reports it.\n *\n * Declared here (the dependency-free leaf) because it is PERSISTED on the\n * account; the parser that produces it lives in `adapter/quota-summary.ts`, which\n * imports this module rather than the reverse.\n */\nexport interface QuotaWindow {\n /** Upstream's own bucket id, e.g. `gemini-5h`, `3p-weekly`. Kept verbatim. */\n bucketId: string\n /** Upstream's window token: `5h` or `weekly` today. */\n window: string\n /** 0..1, or null when upstream omitted the fraction (unknown, not empty). */\n remainingFraction: number | null\n /** RFC3339 reset moment, or null when upstream omitted it. */\n resetTime: string | null\n}\n\n/** One group of models sharing a 5-hour and a weekly window. */\nexport interface QuotaGroup {\n /** Upstream's group label, e.g. `Gemini Models`. */\n name: string\n windows: QuotaWindow[]\n}\n\n/**\n * The grouped 5-hour / weekly windows, cached per account.\n *\n * Deliberately SEPARATE from `cachedQuota`: that map is per-FAMILY and feeds the\n * rotation/ranking path (`familyQuotaFor`, `isFamilyDrained`), while this is\n * per-GROUP and display-only. Merging them would put two different shapes under\n * one key and let a display refresh influence scheduling.\n */\nexport interface CachedLimits {\n groups: QuotaGroup[]\n /** When this snapshot was taken (Unix ms). */\n updatedAt: number\n}\n\n/** One account in the pool. `refresh` is the packed `refreshToken|projectId|managedProjectId` string. */\nexport interface ManagedAccount {\n id?: string\n email?: string\n refresh: string\n projectId?: string\n managedProjectId?: string\n clientId?: string\n addedAt: number\n lastUsed: number\n enabled?: boolean\n rateLimitResetTimes?: Record<string, number>\n coolingDownUntil?: number\n cooldownReason?: CooldownReason\n /**\n * When the CURRENT cooldown began (Unix ms).\n *\n * Separate from `coolingDownUntil`, which is its END: the duration is a\n * backoff computed from the consecutive-failure count, so the start cannot be\n * recovered from the end. Persisted because the reason is only useful with an\n * age attached — \"network-error\" alone cannot distinguish a blip from seconds\n * ago from one that has been sitting there for days. Cleared together with\n * `cooldownReason` when the window expires.\n */\n cooldownSetAt?: number\n verificationRequired?: boolean\n verificationRequiredAt?: number\n verificationRequiredReason?: string\n verificationUrl?: string\n fingerprint?: Fingerprint\n fingerprintHistory?: FingerprintVersion[]\n cachedQuota?: Record<string, CachedQuota>\n cachedQuotaUpdatedAt?: number\n /** Grouped 5h/weekly windows, display-only (see `CachedLimits`). */\n cachedLimits?: CachedLimits\n /** Per-account proxy URL (e.g. http://user:pass@host:8080 or socks5://host:1080). Undefined = follow env. */\n proxy?: string\n}\n\nexport interface AccountStorageV1 {\n version: 1\n accounts: Array<{\n email?: string\n refreshToken: string\n projectId?: string\n managedProjectId?: string\n addedAt: number\n lastUsed: number\n isRateLimited?: boolean\n rateLimitResetTime?: number\n lastSwitchReason?: 'rate-limit' | 'initial' | 'rotation'\n }>\n activeIndex: number\n}\n\nexport interface AccountStorageV2 {\n version: 2\n accounts: Array<{\n email?: string\n refreshToken: string\n projectId?: string\n managedProjectId?: string\n addedAt: number\n lastUsed: number\n lastSwitchReason?: 'rate-limit' | 'initial' | 'rotation'\n rateLimitResetTimes?: Record<string, number>\n }>\n activeIndex: number\n}\n\nexport interface AccountStorageV3 {\n version: 3\n accounts: ManagedAccount[]\n activeIndex: number\n}\n\nexport interface AccountStorageV4 {\n version: 4\n accounts: ManagedAccount[]\n activeIndex: number\n}\n\nexport type AccountStorage = AccountStorageV1 | AccountStorageV2 | AccountStorageV3 | AccountStorageV4\n\n/**\n * How one account-scoped request is routed — and therefore how its transport\n * failures must be read. `proxyUrl` is the single source of truth: fail-closed\n * applies exactly when it is set, so routing and failure classification can\n * never disagree (splitting them into two arguments let them drift).\n */\nexport interface AccountRouting {\n /** Per-account proxy; unset means the env/direct route. */\n proxyUrl?: string\n /**\n * Generation stream: long model silences are normal, so the per-gap body\n * inactivity timer must be disabled (AGENTS.md \"Proxy Routing\").\n */\n streaming?: boolean\n}\n\n/** Whether a request ran through an explicit per-account proxy. */\nexport function isProxyRouted(routing: AccountRouting | undefined): boolean {\n return Boolean(routing?.proxyUrl)\n}\n\n/** Parsed halves of the packed refresh string. */\nexport interface RefreshParts {\n refreshToken?: string\n projectId?: string\n managedProjectId?: string\n}\n\n/** OAuth token view of an account used by the refresh path. */\nexport interface OAuthAuthDetails {\n access: string\n expires: number\n refresh: string\n}\n\n/** Result of the OAuth token exchange. */\nexport interface TokenExchangeSuccess {\n type: 'success'\n refresh: string\n access: string\n expires: number\n email?: string\n projectId: string\n tier?: string\n clientId?: string\n}\n\nexport interface TokenExchangeFailure {\n type: 'failed'\n error: string\n}\n\nexport type TokenExchangeResult = TokenExchangeSuccess | TokenExchangeFailure\n\nexport interface AgyAccountSession {\n auth: OAuthAuthDetails\n account: ManagedAccount\n index: number\n /**\n * Impersonation headers for this request.\n *\n * `clientMetadata` rides alongside rather than inside: it is a BODY message\n * (`metadata` on the control-plane calls), because the `Client-Metadata` header\n * this used to be is present in neither official binary.\n */\n impersonation: {\n 'User-Agent': string\n 'X-Goog-Api-Client': string\n clientMetadata: ClientMetadata\n }\n}\n\n/** Authentication failure while resolving an account session. */\nexport type AgyAuthErrorKind = 'transport' | 'rate-limit' | 'invalid-credential'\n\n/**\n * Host-independent authentication error. The adapter maps `kind` to the DSH\n * error protocol without coupling the session or CLI layers to dsh-llm.\n */\nexport class AgyAuthError extends Error {\n readonly kind: AgyAuthErrorKind\n\n constructor(kind: AgyAuthErrorKind, message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'AgyAuthError'\n this.kind = kind\n }\n}\n\n/** Why an enabled account pool cannot currently serve one model family. */\nexport type PoolBlockedKind = 'retryable' | 'quota-exhausted'\n\n/**\n * Enabled accounts exist, but every candidate is temporarily blocked. Kept\n * independent of dsh-llm so CLI and web entry points do not gain a host import.\n */\nexport class AgyPoolBlockedError extends Error {\n readonly kind: PoolBlockedKind\n readonly blockedUntil: number\n\n constructor(kind: PoolBlockedKind, blockedUntil: number) {\n super(kind === 'quota-exhausted'\n ? 'All agy accounts have exhausted quota for the requested model family.'\n : 'All agy accounts are temporarily blocked for the requested model family.')\n this.name = 'AgyPoolBlockedError'\n this.kind = kind\n this.blockedUntil = blockedUntil\n }\n}\n\n/** Classified upstream failure kinds consumed by the rotation state machine. */\nexport type FailureKind =\n | 'rate-limit'\n | 'auth-failure'\n /**\n * Upstream asked for account verification (`VALIDATION_REQUIRED`) rather than\n * rejecting the credential. Deliberately distinct from `auth-failure` because\n * it is RECOVERABLE: the account is temporarily walled, not dead, and the user\n * can act on the returned URL. Treating it as `auth-failure` permanently\n * disabled a healthy account on a signal that meant \"come back after\n * verifying\", with no automatic way back.\n */\n | 'verification-required'\n | 'network-error'\n | 'project-error'\n | 'request-error'\n | 'transient'\n | 'proxy-unreachable'\n\n/**\n * Rotation state machine decision for one failed attempt.\n *\n * `backoffMs` is **advisory**, and for `retry`/`rotate` no caller consumes it.\n * It is not the mechanism that paces the pool: the account-level `cool` paths\n * write it into `coolingDownUntil` themselves, `rotate` blocks the failed\n * account through `rateLimitResetTimes`, and the delay before the retry of a\n * single request belongs to DSH's retry policy (`providerRetryAfterMs`, else its\n * own exponential `localDelay`). Do NOT \"wire it up\" by feeding it into\n * `providerRetryAfterMs`: a tier above DSH's `maxDelayMs` makes the normal retry\n * mode give up entirely, turning a recoverable 5xx into a failed turn.\n */\nexport type RotationAction =\n | { action: 'retry'; backoffMs: number }\n | { action: 'cool'; backoffMs: number }\n | { action: 'rotate'; backoffMs: number }\n | { action: 'revoke' }\n /** Permanent request-construction error: no state change, surface as-is. */\n | { action: 'noop' }\n","/**\n * Antigravity (agy) OAuth and API constants.\n *\n * The client id/secret below are the public Google consumer-OAuth credentials\n * shipped inside the Antigravity desktop product and its `agy` CLI; they are\n * embedded in many public tools (see NOTICE.md). They are not secrets owned by\n * this project.\n */\n\nimport { proxiedFetch } from '../proxy.ts'\nimport { isProxyRouted } from '../types.ts'\nimport type { AccountRouting } from '../types.ts'\n\nexport const AGY_CLIENT_ID =\n '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com'\n\nexport const AGY_CLIENT_SECRET = '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'\n\n/**\n * Effective OAuth client credentials: AGY_CLIENT_ID / AGY_CLIENT_SECRET env\n * overrides win when set (BYO OAuth app escape hatch, mirrors pi-antigravity);\n * otherwise the embedded public Antigravity credentials are used.\n */\nexport function resolveAgyClientCredentials(overrideClientId?: string): { clientId: string; clientSecret: string } {\n if (overrideClientId) {\n if (overrideClientId === AGY_CLIENT_ID) {\n return { clientId: AGY_CLIENT_ID, clientSecret: AGY_CLIENT_SECRET }\n }\n return {\n clientId: overrideClientId,\n clientSecret: process.env.AGY_CLIENT_SECRET || AGY_CLIENT_SECRET,\n }\n }\n return {\n clientId: process.env.AGY_CLIENT_ID || AGY_CLIENT_ID,\n clientSecret: process.env.AGY_CLIENT_SECRET || AGY_CLIENT_SECRET,\n }\n}\n\n/** Required scopes. `openid` must NOT be added: it routes Google into the hanging\n * `firstparty/nativeapp` consent for this client (verified by OmniRoute). */\nexport const AGY_SCOPES: readonly string[] = [\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\nexport const OAUTH_AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/v2/auth'\nexport const OAUTH_TOKEN_URL = 'https://oauth2.googleapis.com/token'\nexport const OAUTH_USERINFO_URL = 'https://www.googleapis.com/oauth2/v1/userinfo'\n\n/** Default loopback callback used by the standalone CLI listener (fixed port, like opencode). */\nexport const AGY_DEFAULT_REDIRECT_URI = 'http://localhost:51121/oauth-callback'\n\n/**\n * Antigravity API endpoints. The daily runtime host (no .sandbox suffix) is the\n * live endpoint for consumer OAuth accounts — cloudcode-pa.googleapis.com\n * answers RESOURCE_EXHAUSTED for them (verified by live probe), while the\n * daily host answers 200. Order matters: first reachable non-429/403/503 wins.\n */\nexport const AGY_ENDPOINT_DAILY = 'https://daily-cloudcode-pa.googleapis.com'\nexport const AGY_ENDPOINT_PROD = 'https://cloudcode-pa.googleapis.com'\nexport const AGY_ENDPOINT_DAILY_SANDBOX = 'https://daily-cloudcode-pa.sandbox.googleapis.com'\nexport const AGY_ENDPOINT_AUTOPUSH = 'https://autopush-cloudcode-pa.sandbox.googleapis.com'\n\n/** Runtime/bootstrap endpoint fallback order (daily first, mirroring OmniRoute). */\nexport const AGY_ENDPOINT_FALLBACKS: readonly string[] = [\n AGY_ENDPOINT_DAILY,\n AGY_ENDPOINT_PROD,\n AGY_ENDPOINT_DAILY_SANDBOX,\n AGY_ENDPOINT_AUTOPUSH,\n]\n\n/**\n * Statuses that mean \"this endpoint is not usable for this attempt\"; skip to the\n * next one in the chain. 429/403 = rate/quota/entitlement wall, 503 = capacity\n * rejection (e.g. \"No capacity available for model ...\"). A 503 skipped here\n * still reaches the caller when every endpoint fails, where the classifier marks\n * it transient and the adapter surfaces it as a retryable SERVER error.\n */\nexport const AGY_ENDPOINT_SKIP_STATUSES = new Set([429, 403, 503])\n\n/** Routing of the requests being tried (see {@link AccountRouting}). */\nexport type AgyFallbackOptions = Pick<AccountRouting, 'proxyUrl'>\n/**\n * Try each runtime endpoint in order, skipping unusable ones (429/403/503/network).\n * Returns the first other response (2xx or a real error like 400/401); when\n * every endpoint is unusable, returns the last skipped response so the caller's\n * classifier can still produce a meaningful error (a returned 503 becomes a\n * retryable SERVER failure rather than being swallowed here).\n *\n * When every endpoint fails at the network level, the LAST error is rethrown so\n * its cause survives (a generic \"all endpoints failed\" hid DNS/TLS/timeout).\n */\nexport async function fetchAgyFirstOk(\n urlPath: string,\n init: RequestInit,\n fetchImpl: typeof fetch = proxiedFetch,\n options: AgyFallbackOptions = {},\n): Promise<Response> {\n let lastSkipped: Response | null = null\n let lastNetworkError: unknown\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const response = await fetchImpl(`${baseEndpoint}${urlPath}`, init)\n if (!AGY_ENDPOINT_SKIP_STATUSES.has(response.status)) return response\n lastSkipped = response\n } catch (error) {\n lastNetworkError = error\n // Fail-closed only while an explicit per-account proxy is in effect: the\n // request must not be retried on another endpoint or go direct.\n if (isProxyRouted(options)) {\n const { isProxyUnreachableError } = await import('../proxy.ts')\n if (isProxyUnreachableError(error)) throw error\n }\n // network error — try the next endpoint\n }\n }\n if (lastSkipped) return lastSkipped\n if (lastNetworkError !== undefined) throw lastNetworkError\n throw new Error('all agy endpoints failed')\n}\n\n/**\n * Pinned Antigravity version, used only until the runtime resolver has published\n * a fresh one (see {@link setResolvedAgyVersion}).\n *\n * A stale version string is the most detectable fingerprint anomaly, so this\n * value is a cold-start floor, not the version anything should normally send.\n * It tracks the release feed's newest entry; refresh it when the feed moves.\n *\n * NAMESPACE: this is the **Antigravity CLI** line (`google-antigravity/antigravity-cli`),\n * not the IDE or hub line, because that is the product this client claims to be\n * (`docs/official-identity.json`). The three lines are separate version\n * namespaces — IDE `2.0.0`, hub `2.15.1`, CLI `1.2.9` — so comparing them\n * numerically is meaningless and mixing them advertises a version that does not\n * exist for the product we name.\n */\nexport const AGY_VERSION_FALLBACK = '1.2.9'\n\n/**\n * `ClientMetadata.platform` value: the proto enum NAME, as protobuf-JSON emits it.\n *\n * The official enumeration is `PLATFORM_UNSPECIFIED | DARWIN_AMD64 | DARWIN_ARM64\n * | LINUX_AMD64 | LINUX_ARM64 | WINDOWS_AMD64` (read from the installed official\n * CLI's own descriptor). The earlier `\"MACOS\"` that this backend rejected with\n * `INVALID_ARGUMENT` was an invalid *value*, not a forbidden field — a\n * distinction that mattered, because it had been read as \"send `ideType` only\".\n */\nexport const AGY_PLATFORM_ENUM = 'DARWIN_ARM64'\n\n/**\n * Newest Antigravity version the runtime resolver has observed, or undefined\n * before its first success.\n *\n * Deliberately lives in this module — the dependency leaf — rather than in\n * `runtime/version.ts`. `oauth/` must not import `runtime/` (AGENTS.md), and the\n * edge already runs the other way: `runtime/version.ts` imports THIS file. So the\n * resolver publishes here and every User-Agent builder reads here.\n *\n * This shape is the fix for a real defect: the version used to be a function\n * parameter, and all six bootstrap call sites silently took the default, pinning\n * every control-plane request to the stale fallback while the data plane used the\n * resolved version — one process presenting two different clients. Reading a\n * shared value makes that drift unrepresentable rather than merely discouraged.\n */\nlet resolvedAgyVersion: string | undefined\n\n/** Publish a freshly resolved version for every User-Agent builder. */\nexport function setResolvedAgyVersion(version: string | undefined): void {\n resolvedAgyVersion = version\n}\n\n/** Version a User-Agent should advertise: the resolved one, else the pinned floor. */\nexport function currentAgyVersion(): string {\n return resolvedAgyVersion ?? AGY_VERSION_FALLBACK\n}\n\n/** Electron-style UA used for bootstrap calls (loadCodeAssist/onboardUser). */\nexport function getAgyBootstrapUserAgent(version = currentAgyVersion()): string {\n 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`\n}\n\n/**\n * Short-form client User-Agent: `antigravity/<version> <platform>`.\n *\n * Used where the full Electron string would be inappropriate but the request\n * must still not self-identify as this tool (e.g. the version feeds, which the\n * official client ecosystem also polls).\n */\nexport function antigravityUserAgent(version = currentAgyVersion(), platform = 'darwin/arm64'): string {\n return `antigravity/${version} ${platform}`\n}\n\n/**\n * `ClientMetadata.ideType` value.\n *\n * `ANTIGRAVITY` is retained rather than switched to the enumeration's newer\n * `GEMINI_CLI`: the CLI's own `IdeType` is injected through its auth provider and\n * has not been captured, so the established working value is kept and the\n * alternative recorded (`docs/official-identity.json`) rather than guessed at.\n */\nexport const AGY_IDE_TYPE = 'ANTIGRAVITY'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA0GA,MAAa,gBAA+E;CAC1F,MAAM;EAAE,MAAM;EAAG,MAAM;CAAU;CACjC,OAAO,EAAE,MAAM,EAAE;CACjB,QAAQ;EAAE,MAAM;EAAG,MAAM;CAAS;CAClC,SAAS,EAAE,MAAM,EAAE;AACrB;;AA8IA,SAAgB,cAAc,SAA8C;CAC1E,OAAO,QAAQ,SAAS,QAAQ;AAClC;;;;;AA4DA,IAAa,eAAb,cAAkC,MAAM;CACtC;CAEA,YAAY,MAAwB,SAAiB,SAAwB;EAC3E,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;;;;;AASA,IAAa,sBAAb,cAAyC,MAAM;CAC7C;CACA;CAEA,YAAY,MAAuB,cAAsB;EACvD,MAAM,SAAS,oBACX,0EACA,0EAA0E;EAC9E,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,eAAe;CACtB;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3UA,MAAa,gBACX;AAEF,MAAa,oBAAoB;;;;;;AAOjC,SAAgB,4BAA4B,kBAAuE;CACjH,IAAI,kBAAkB;EACpB,IAAI,qBAAA,6EACF,OAAO;GAAE,UAAU;GAAe,cAAc;EAAkB;EAEpE,OAAO;GACL,UAAU;GACV,cAAc,QAAQ,IAAI,qBAAA;EAC5B;CACF;CACA,OAAO;EACL,UAAU,QAAQ,IAAI,iBAAA;EACtB,cAAc,QAAQ,IAAI,qBAAA;CAC5B;AACF;;;AAIA,MAAa,aAAgC;CAC3C;CACA;CACA;CACA;CACA;AACF;AAEA,MAAa,sBAAsB;AACnC,MAAa,kBAAkB;AAC/B,MAAa,qBAAqB;;AAGlC,MAAa,2BAA2B;;;;;;;AAQxC,MAAa,qBAAqB;AAClC,MAAa,oBAAoB;AACjC,MAAa,6BAA6B;AAC1C,MAAa,wBAAwB;;AAGrC,MAAa,yBAA4C;CACvD;CACA;CACA;CACA;AACF;;;;;;;;AASA,MAAa,6CAA6B,IAAI,IAAI;CAAC;CAAK;CAAK;AAAG,CAAC;;;;;;;;;;;AAcjE,eAAsB,gBACpB,SACA,MACA,YAA0B,cAC1B,UAA8B,CAAC,GACZ;CACnB,IAAI,cAA+B;CACnC,IAAI;CACJ,KAAK,MAAM,gBAAgB,wBACzB,IAAI;EACF,MAAM,WAAW,MAAM,UAAU,GAAG,eAAe,WAAW,IAAI;EAClE,IAAI,CAAC,2BAA2B,IAAI,SAAS,MAAM,GAAG,OAAO;EAC7D,cAAc;CAChB,SAAS,OAAO;EACd,mBAAmB;EAGnB,IAAI,cAAc,OAAO,GAAG;GAC1B,MAAM,EAAE,4BAA4B,MAAM,OAAO,uBAAc,CAAA,MAAA,MAAA,EAAA,CAAA;GAC/D,IAAI,wBAAwB,KAAK,GAAG,MAAM;EAC5C;CAEF;CAEF,IAAI,aAAa,OAAO;CACxB,IAAI,qBAAqB,KAAA,GAAW,MAAM;CAC1C,MAAM,IAAI,MAAM,0BAA0B;AAC5C;;;;;;;;;;;;;;;;AAiBA,MAAa,uBAAuB;;;;;;;;;;AAWpC,MAAa,oBAAoB;;;;;;;;;;;;;;;;AAiBjC,IAAI;;AAGJ,SAAgB,sBAAsB,SAAmC;CACvE,qBAAqB;AACvB;;AAGA,SAAgB,oBAA4B;CAC1C,OAAO,sBAAA;AACT;;AAGA,SAAgB,yBAAyB,UAAU,kBAAkB,GAAW;CAC9E,OAAO,gGAAgG,QAAQ;AACjH;;;;;;;;AASA,SAAgB,qBAAqB,UAAU,kBAAkB,GAAG,WAAW,gBAAwB;CACrG,OAAO,eAAe,QAAQ,GAAG;AACnC;;;;;;;;;AAUA,MAAa,eAAe"}
1
+ {"version":3,"file":"constants-AdsD4sKh.mjs","names":[],"sources":["../src/types.ts","../src/oauth/constants.ts"],"sourcesContent":["/** Shared domain types for dsh-agy. */\n\n/**\n * Device fingerprint persisted per account (rate-limit mitigation).\n *\n * Mirrors `google.internal.cloud.code.v1internal.ClientMetadata`, read out of the\n * installed official CLI's own descriptor (`docs/official-identity.json`). The\n * message has eight fields; only the three below are populated, because the rest\n * (`pluginVersion`, `updateChannel`, `duetProject`, `pluginType`, `ideName`) have\n * no captured value and a wrong value is a worse anomaly than an absent one.\n *\n * This is the BODY message (`metadata`), not a header. Nothing here is sent as a\n * `Client-Metadata` header: neither official binary contains that header name.\n */\nexport interface ClientMetadata {\n ideType: string\n /** Client version for the claimed product line (the CLI's, not the IDE's). */\n ideVersion?: string\n /**\n * `ClientMetadata.platform` enum NAME (`DARWIN_ARM64`), which is a DIFFERENT\n * vocabulary from the UA's `darwin/arm64` token — both exist, and conflating\n * them is how `\"MACOS\"` came to be sent and rejected.\n */\n platform?: string\n}\n\nexport interface Fingerprint {\n deviceId: string\n sessionToken: string\n userAgent: string\n apiClient: string\n clientMetadata: ClientMetadata\n createdAt: number\n}\n\nexport interface FingerprintVersion {\n fingerprint: Fingerprint\n timestamp: number\n reason: 'initial' | 'regenerated' | 'restored'\n}\n\nexport type CooldownReason =\n | 'auth-failure'\n | 'network-error'\n | 'project-error'\n | 'quota-exhausted'\n | 'validation-required'\n\n/**\n * Per-account quota cache keyed by model FAMILY (`google` / `anthropic` /\n * `openai`, or `unknown`) — never by model id. See `familyKeyOf` in\n * `runtime/quota.ts`.\n *\n * The keying is load-bearing for the fields below: a family has exactly ONE\n * record, so a family-scoped reading stored here cannot disagree with a\n * per-model copy of itself, and there is no second key to keep in sync.\n */\nexport interface CachedQuota {\n /** 0..1 left in the family's rolling 5-hour window. */\n remainingFraction?: number\n /** When the 5-hour window refills (RFC3339). */\n resetTime?: string\n /**\n * 0..1 left in the family's 7-day window, when `retrieveUserQuotaSummary`\n * reported one.\n *\n * A genuinely SEPARATE window, not a second view of `remainingFraction`: the\n * 5-hour bucket refills four times a day while the weekly budget only drains,\n * so an account can be comfortable on one and exhausted on the other.\n *\n * It always travels with its own `weeklyResetTime`, and that timestamp is what\n * bounds the value's life: once it passes, every consumer ignores the reading\n * (`isFamilyDrained`, `parseFutureResetMs`, `rankPoolCandidates`), so a weekly\n * value carried forward across a failed probe cannot outlive its window.\n */\n weeklyFraction?: number\n /** When the 7-day window refills (RFC3339). */\n weeklyResetTime?: string\n /** How many models the per-model probe contributed to this family's reading. */\n modelCount?: number\n}\n\n/**\n * The upstream `window` tokens we understand: their ORDER by duration, and which\n * of them `CachedQuota` has a field for.\n *\n * ONE table for a vocabulary that used to live in two places giving two different\n * answers: `adapter/quota-summary.ts` sorted by its own token table while\n * `runtime/quota.ts` classified windows with `includes()` checks. The\n * disagreement was not academic — `daily` existed in one and not the other, so a\n * daily group parsed and display-sorted correctly, then contributed an EMPTY\n * record that replaced a real measurement.\n *\n * Keeping both facts in one row is deliberate: a token cannot be rankable in one\n * consumer and unknown to the other, because there is only one place to add it.\n * A missing `kind` means the cache has nowhere to put that reading, which is what\n * stops the empty-record overwrite.\n *\n * Declared in this leaf rather than in either consumer because BOTH need it:\n * `runtime/` must not reach into `adapter/`, and the persisted `QuotaWindow` is\n * already defined here.\n *\n * `rank` is explicit rather than inferred from token length so it states the\n * intended duration ordering: `daily` (5 chars) would otherwise sort before\n * `weekly` (6) for the wrong reason.\n */\nexport const QUOTA_WINDOWS: Record<string, { rank: number, kind?: 'rolling' | 'weekly' }> = {\n '5h': { rank: 0, kind: 'rolling' },\n daily: { rank: 1 },\n weekly: { rank: 2, kind: 'weekly' },\n monthly: { rank: 3 },\n}\n\n/**\n * One window of one `QuotaGroup`, as `retrieveUserQuotaSummary` reports it.\n *\n * Declared here (the dependency-free leaf) because it is PERSISTED on the\n * account; the parser that produces it lives in `adapter/quota-summary.ts`, which\n * imports this module rather than the reverse.\n */\nexport interface QuotaWindow {\n /** Upstream's own bucket id, e.g. `gemini-5h`, `3p-weekly`. Kept verbatim. */\n bucketId: string\n /** Upstream's window token: `5h` or `weekly` today. */\n window: string\n /** 0..1, or null when upstream omitted the fraction (unknown, not empty). */\n remainingFraction: number | null\n /** RFC3339 reset moment, or null when upstream omitted it. */\n resetTime: string | null\n}\n\n/** One group of models sharing a 5-hour and a weekly window. */\nexport interface QuotaGroup {\n /** Upstream's group label, e.g. `Gemini Models`. */\n name: string\n windows: QuotaWindow[]\n}\n\n/**\n * The grouped 5-hour / weekly windows, cached per account.\n *\n * Deliberately SEPARATE from `cachedQuota`: that map is per-FAMILY and feeds the\n * rotation/ranking path (`familyQuotaFor`, `isFamilyDrained`), while this is\n * per-GROUP and display-only. Merging them would put two different shapes under\n * one key and let a display refresh influence scheduling.\n */\nexport interface CachedLimits {\n groups: QuotaGroup[]\n /** When this snapshot was taken (Unix ms). */\n updatedAt: number\n}\n\n/** One account in the pool. `refresh` is the packed `refreshToken|projectId|managedProjectId` string. */\nexport interface ManagedAccount {\n id?: string\n email?: string\n refresh: string\n projectId?: string\n managedProjectId?: string\n clientId?: string\n addedAt: number\n lastUsed: number\n enabled?: boolean\n rateLimitResetTimes?: Record<string, number>\n coolingDownUntil?: number\n cooldownReason?: CooldownReason\n /**\n * When the CURRENT cooldown began (Unix ms).\n *\n * Separate from `coolingDownUntil`, which is its END: the duration is a\n * backoff computed from the consecutive-failure count, so the start cannot be\n * recovered from the end. Persisted because the reason is only useful with an\n * age attached — \"network-error\" alone cannot distinguish a blip from seconds\n * ago from one that has been sitting there for days. Cleared together with\n * `cooldownReason` when the window expires.\n */\n cooldownSetAt?: number\n verificationRequired?: boolean\n verificationRequiredAt?: number\n verificationRequiredReason?: string\n verificationUrl?: string\n fingerprint?: Fingerprint\n fingerprintHistory?: FingerprintVersion[]\n cachedQuota?: Record<string, CachedQuota>\n cachedQuotaUpdatedAt?: number\n /** Grouped 5h/weekly windows, display-only (see `CachedLimits`). */\n cachedLimits?: CachedLimits\n /** Per-account proxy URL (e.g. http://user:pass@host:8080 or socks5://host:1080). Undefined = follow env. */\n proxy?: string\n}\n\nexport interface AccountStorageV1 {\n version: 1\n accounts: Array<{\n email?: string\n refreshToken: string\n projectId?: string\n managedProjectId?: string\n addedAt: number\n lastUsed: number\n isRateLimited?: boolean\n rateLimitResetTime?: number\n lastSwitchReason?: 'rate-limit' | 'initial' | 'rotation'\n }>\n activeIndex: number\n}\n\nexport interface AccountStorageV2 {\n version: 2\n accounts: Array<{\n email?: string\n refreshToken: string\n projectId?: string\n managedProjectId?: string\n addedAt: number\n lastUsed: number\n lastSwitchReason?: 'rate-limit' | 'initial' | 'rotation'\n rateLimitResetTimes?: Record<string, number>\n }>\n activeIndex: number\n}\n\nexport interface AccountStorageV3 {\n version: 3\n accounts: ManagedAccount[]\n activeIndex: number\n}\n\nexport interface AccountStorageV4 {\n version: 4\n accounts: ManagedAccount[]\n activeIndex: number\n}\n\nexport type AccountStorage = AccountStorageV1 | AccountStorageV2 | AccountStorageV3 | AccountStorageV4\n\n/**\n * How one account-scoped request is routed — and therefore how its transport\n * failures must be read. `proxyUrl` is the single source of truth: fail-closed\n * applies exactly when it is set, so routing and failure classification can\n * never disagree (splitting them into two arguments let them drift).\n */\nexport interface AccountRouting {\n /** Per-account proxy; unset means the env/direct route. */\n proxyUrl?: string\n /**\n * Generation stream: long model silences are normal, so the per-gap body\n * inactivity timer must be disabled (AGENTS.md \"Proxy Routing\").\n */\n streaming?: boolean\n}\n\n/** Whether a request ran through an explicit per-account proxy. */\nexport function isProxyRouted(routing: AccountRouting | undefined): boolean {\n return Boolean(routing?.proxyUrl)\n}\n\n/** Parsed halves of the packed refresh string. */\nexport interface RefreshParts {\n refreshToken?: string\n projectId?: string\n managedProjectId?: string\n}\n\n/** OAuth token view of an account used by the refresh path. */\nexport interface OAuthAuthDetails {\n access: string\n expires: number\n refresh: string\n}\n\n/** Result of the OAuth token exchange. */\nexport interface TokenExchangeSuccess {\n type: 'success'\n refresh: string\n access: string\n expires: number\n email?: string\n projectId: string\n tier?: string\n clientId?: string\n}\n\nexport interface TokenExchangeFailure {\n type: 'failed'\n error: string\n}\n\nexport type TokenExchangeResult = TokenExchangeSuccess | TokenExchangeFailure\n\nexport interface AgyAccountSession {\n auth: OAuthAuthDetails\n account: ManagedAccount\n index: number\n /**\n * Impersonation headers for this request.\n *\n * `clientMetadata` rides alongside rather than inside: it is a BODY message\n * (`metadata` on the control-plane calls), because the `Client-Metadata` header\n * this used to be is present in neither official binary.\n */\n impersonation: {\n 'User-Agent': string\n 'X-Goog-Api-Client': string\n clientMetadata: ClientMetadata\n }\n}\n\n/** Authentication failure while resolving an account session. */\nexport type AgyAuthErrorKind = 'transport' | 'rate-limit' | 'invalid-credential'\n\n/**\n * Host-independent authentication error. The adapter maps `kind` to the DSH\n * error protocol without coupling the session or CLI layers to dsh-llm.\n */\nexport class AgyAuthError extends Error {\n readonly kind: AgyAuthErrorKind\n\n constructor(kind: AgyAuthErrorKind, message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'AgyAuthError'\n this.kind = kind\n }\n}\n\n/** Why an enabled account pool cannot currently serve one model family. */\nexport type PoolBlockedKind = 'retryable' | 'quota-exhausted'\n\n/**\n * Enabled accounts exist, but every candidate is temporarily blocked. Kept\n * independent of dsh-llm so CLI and web entry points do not gain a host import.\n */\nexport class AgyPoolBlockedError extends Error {\n readonly kind: PoolBlockedKind\n readonly blockedUntil: number\n\n constructor(kind: PoolBlockedKind, blockedUntil: number) {\n super(kind === 'quota-exhausted'\n ? 'All agy accounts have exhausted quota for the requested model family.'\n : 'All agy accounts are temporarily blocked for the requested model family.')\n this.name = 'AgyPoolBlockedError'\n this.kind = kind\n this.blockedUntil = blockedUntil\n }\n}\n\n/** Classified upstream failure kinds consumed by the rotation state machine. */\nexport type FailureKind =\n | 'rate-limit'\n | 'auth-failure'\n /**\n * Upstream asked for account verification (`VALIDATION_REQUIRED`) rather than\n * rejecting the credential. Deliberately distinct from `auth-failure` because\n * it is RECOVERABLE: the account is temporarily walled, not dead, and the user\n * can act on the returned URL. Treating it as `auth-failure` permanently\n * disabled a healthy account on a signal that meant \"come back after\n * verifying\", with no automatic way back.\n */\n | 'verification-required'\n | 'network-error'\n | 'project-error'\n | 'request-error'\n | 'transient'\n | 'proxy-unreachable'\n\n/**\n * Rotation state machine decision for one failed attempt.\n *\n * `backoffMs` is **advisory**, and for `retry`/`rotate` no caller consumes it.\n * It is not the mechanism that paces the pool: the account-level `cool` paths\n * write it into `coolingDownUntil` themselves, `rotate` blocks the failed\n * account through `rateLimitResetTimes`, and the delay before the retry of a\n * single request belongs to DSH's retry policy (`providerRetryAfterMs`, else its\n * own exponential `localDelay`). Do NOT \"wire it up\" by feeding it into\n * `providerRetryAfterMs`: a tier above DSH's `maxDelayMs` makes the normal retry\n * mode give up entirely, turning a recoverable 5xx into a failed turn.\n */\nexport type RotationAction =\n | { action: 'retry'; backoffMs: number }\n | { action: 'cool'; backoffMs: number }\n | { action: 'rotate'; backoffMs: number }\n | { action: 'revoke' }\n /** Permanent request-construction error: no state change, surface as-is. */\n | { action: 'noop' }\n","/**\n * Antigravity (agy) OAuth and API constants.\n *\n * The client id/secret below are the public Google consumer-OAuth credentials\n * shipped inside the Antigravity desktop product and its `agy` CLI; they are\n * embedded in many public tools (see NOTICE.md). They are not secrets owned by\n * this project.\n */\n\nimport { proxiedFetch } from '../proxy.ts'\nimport { isProxyRouted } from '../types.ts'\nimport type { AccountRouting } from '../types.ts'\n\nexport const AGY_CLIENT_ID =\n '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com'\n\nexport const AGY_CLIENT_SECRET = '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'\n\n/**\n * Effective OAuth client credentials: AGY_CLIENT_ID / AGY_CLIENT_SECRET env\n * overrides win when set (BYO OAuth app escape hatch, mirrors pi-antigravity);\n * otherwise the embedded public Antigravity credentials are used.\n */\nexport function resolveAgyClientCredentials(overrideClientId?: string): { clientId: string; clientSecret: string } {\n if (overrideClientId) {\n if (overrideClientId === AGY_CLIENT_ID) {\n return { clientId: AGY_CLIENT_ID, clientSecret: AGY_CLIENT_SECRET }\n }\n return {\n clientId: overrideClientId,\n clientSecret: process.env.AGY_CLIENT_SECRET || AGY_CLIENT_SECRET,\n }\n }\n return {\n clientId: process.env.AGY_CLIENT_ID || AGY_CLIENT_ID,\n clientSecret: process.env.AGY_CLIENT_SECRET || AGY_CLIENT_SECRET,\n }\n}\n\n/** Required scopes. `openid` must NOT be added: it routes Google into the hanging\n * `firstparty/nativeapp` consent for this client (verified by OmniRoute). */\nexport const AGY_SCOPES: readonly string[] = [\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\nexport const OAUTH_AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/v2/auth'\nexport const OAUTH_TOKEN_URL = 'https://oauth2.googleapis.com/token'\nexport const OAUTH_USERINFO_URL = 'https://www.googleapis.com/oauth2/v1/userinfo'\n\n/** Default loopback callback used by the standalone CLI listener (fixed port, like opencode). */\nexport const AGY_DEFAULT_REDIRECT_URI = 'http://localhost:51121/oauth-callback'\n\n/**\n * Antigravity API endpoints. The daily runtime host (no .sandbox suffix) is the\n * live endpoint for consumer OAuth accounts — cloudcode-pa.googleapis.com\n * answers RESOURCE_EXHAUSTED for them (verified by live probe), while the\n * daily host answers 200. Order matters: first reachable non-429/403/503 wins.\n */\nexport const AGY_ENDPOINT_DAILY = 'https://daily-cloudcode-pa.googleapis.com'\nexport const AGY_ENDPOINT_PROD = 'https://cloudcode-pa.googleapis.com'\nexport const AGY_ENDPOINT_DAILY_SANDBOX = 'https://daily-cloudcode-pa.sandbox.googleapis.com'\nexport const AGY_ENDPOINT_AUTOPUSH = 'https://autopush-cloudcode-pa.sandbox.googleapis.com'\n\n/** Runtime/bootstrap endpoint fallback order (daily first, mirroring OmniRoute). */\nexport const AGY_ENDPOINT_FALLBACKS: readonly string[] = [\n AGY_ENDPOINT_DAILY,\n AGY_ENDPOINT_PROD,\n AGY_ENDPOINT_DAILY_SANDBOX,\n AGY_ENDPOINT_AUTOPUSH,\n]\n\n/**\n * Statuses that mean \"this endpoint is not usable for this attempt\"; skip to the\n * next one in the chain. 429/403 = rate/quota/entitlement wall, 503 = capacity\n * rejection (e.g. \"No capacity available for model ...\"). A 503 skipped here\n * still reaches the caller when every endpoint fails, where the classifier marks\n * it transient and the adapter surfaces it as a retryable SERVER error.\n */\nexport const AGY_ENDPOINT_SKIP_STATUSES = new Set([429, 403, 503])\n\n/** Routing of the requests being tried (see {@link AccountRouting}). */\nexport type AgyFallbackOptions = Pick<AccountRouting, 'proxyUrl'>\n/**\n * Try each runtime endpoint in order, skipping unusable ones (429/403/503/network).\n * Returns the first other response (2xx or a real error like 400/401); when\n * every endpoint is unusable, returns the last skipped response so the caller's\n * classifier can still produce a meaningful error (a returned 503 becomes a\n * retryable SERVER failure rather than being swallowed here).\n *\n * When every endpoint fails at the network level, the LAST error is rethrown so\n * its cause survives (a generic \"all endpoints failed\" hid DNS/TLS/timeout).\n */\nexport async function fetchAgyFirstOk(\n urlPath: string,\n init: RequestInit,\n fetchImpl: typeof fetch = proxiedFetch,\n options: AgyFallbackOptions = {},\n): Promise<Response> {\n let lastSkipped: Response | null = null\n let lastNetworkError: unknown\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const response = await fetchImpl(`${baseEndpoint}${urlPath}`, init)\n if (!AGY_ENDPOINT_SKIP_STATUSES.has(response.status)) return response\n lastSkipped = response\n } catch (error) {\n lastNetworkError = error\n // Fail-closed only while an explicit per-account proxy is in effect: the\n // request must not be retried on another endpoint or go direct.\n if (isProxyRouted(options)) {\n const { isProxyUnreachableError } = await import('../proxy.ts')\n if (isProxyUnreachableError(error)) throw error\n }\n // network error — try the next endpoint\n }\n }\n if (lastSkipped) return lastSkipped\n if (lastNetworkError !== undefined) throw lastNetworkError\n throw new Error('all agy endpoints failed')\n}\n\n/**\n * Pinned Antigravity version, used only until the runtime resolver has published\n * a fresh one (see {@link setResolvedAgyVersion}).\n *\n * A stale version string is the most detectable fingerprint anomaly, so this\n * value is a cold-start floor, not the version anything should normally send.\n * It tracks the release feed's newest entry; refresh it when the feed moves.\n *\n * NAMESPACE: this is the **Antigravity CLI** line (`google-antigravity/antigravity-cli`),\n * not the IDE or hub line, because that is the product this client claims to be\n * (`docs/official-identity.json`). The three lines are separate version\n * namespaces — IDE `2.0.0`, hub `2.15.1`, CLI `1.2.9` — so comparing them\n * numerically is meaningless and mixing them advertises a version that does not\n * exist for the product we name.\n */\nexport const AGY_VERSION_FALLBACK = '1.2.9'\n\n/**\n * `ClientMetadata.platform` value: the proto enum NAME, as protobuf-JSON emits it.\n *\n * The official enumeration is `PLATFORM_UNSPECIFIED | DARWIN_AMD64 | DARWIN_ARM64\n * | LINUX_AMD64 | LINUX_ARM64 | WINDOWS_AMD64` (read from the installed official\n * CLI's own descriptor). The earlier `\"MACOS\"` that this backend rejected with\n * `INVALID_ARGUMENT` was an invalid *value*, not a forbidden field — a\n * distinction that mattered, because it had been read as \"send `ideType` only\".\n */\nexport const AGY_PLATFORM_ENUM = 'DARWIN_ARM64'\n\n/**\n * Newest Antigravity version the runtime resolver has observed, or undefined\n * before its first success.\n *\n * Deliberately lives in this module — the dependency leaf — rather than in\n * `runtime/version.ts`. `oauth/` must not import `runtime/` (AGENTS.md), and the\n * edge already runs the other way: `runtime/version.ts` imports THIS file. So the\n * resolver publishes here and every User-Agent builder reads here.\n *\n * This shape is the fix for a real defect: the version used to be a function\n * parameter, and all six bootstrap call sites silently took the default, pinning\n * every control-plane request to the stale fallback while the data plane used the\n * resolved version — one process presenting two different clients. Reading a\n * shared value makes that drift unrepresentable rather than merely discouraged.\n */\nlet resolvedAgyVersion: string | undefined\n\n/** Publish a freshly resolved version for every User-Agent builder. */\nexport function setResolvedAgyVersion(version: string | undefined): void {\n resolvedAgyVersion = version\n}\n\n/** Version a User-Agent should advertise: the resolved one, else the pinned floor. */\nexport function currentAgyVersion(): string {\n return resolvedAgyVersion ?? AGY_VERSION_FALLBACK\n}\n\n/** Electron-style UA used for bootstrap calls (loadCodeAssist/onboardUser). */\nexport function getAgyBootstrapUserAgent(version = currentAgyVersion()): string {\n 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`\n}\n\n/**\n * Short-form client User-Agent: `antigravity/<version> <platform>`.\n *\n * Used where the full Electron string would be inappropriate but the request\n * must still not self-identify as this tool (e.g. the version feeds, which the\n * official client ecosystem also polls).\n */\nexport function antigravityUserAgent(version = currentAgyVersion(), platform = 'darwin/arm64'): string {\n return `antigravity/${version} ${platform}`\n}\n\n/**\n * `ClientMetadata.ideType` value.\n *\n * `ANTIGRAVITY` is retained rather than switched to the enumeration's newer\n * `GEMINI_CLI`: the CLI's own `IdeType` is injected through its auth provider and\n * has not been captured, so the established working value is kept and the\n * alternative recorded (`docs/official-identity.json`) rather than guessed at.\n */\nexport const AGY_IDE_TYPE = 'ANTIGRAVITY'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA0GA,MAAa,gBAA+E;CAC1F,MAAM;EAAE,MAAM;EAAG,MAAM;CAAU;CACjC,OAAO,EAAE,MAAM,EAAE;CACjB,QAAQ;EAAE,MAAM;EAAG,MAAM;CAAS;CAClC,SAAS,EAAE,MAAM,EAAE;AACrB;;AA8IA,SAAgB,cAAc,SAA8C;CAC1E,OAAO,QAAQ,SAAS,QAAQ;AAClC;;;;;AA4DA,IAAa,eAAb,cAAkC,MAAM;CACtC;CAEA,YAAY,MAAwB,SAAiB,SAAwB;EAC3E,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;;;;;AASA,IAAa,sBAAb,cAAyC,MAAM;CAC7C;CACA;CAEA,YAAY,MAAuB,cAAsB;EACvD,MAAM,SAAS,oBACX,0EACA,0EAA0E;EAC9E,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,eAAe;CACtB;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3UA,MAAa,gBACX;AAEF,MAAa,oBAAoB;;;;;;AAOjC,SAAgB,4BAA4B,kBAAuE;CACjH,IAAI,kBAAkB;EACpB,IAAI,qBAAA,6EACF,OAAO;GAAE,UAAU;GAAe,cAAc;EAAkB;EAEpE,OAAO;GACL,UAAU;GACV,cAAc,QAAQ,IAAI,qBAAA;EAC5B;CACF;CACA,OAAO;EACL,UAAU,QAAQ,IAAI,iBAAA;EACtB,cAAc,QAAQ,IAAI,qBAAA;CAC5B;AACF;;;AAIA,MAAa,aAAgC;CAC3C;CACA;CACA;CACA;CACA;AACF;AAEA,MAAa,sBAAsB;AACnC,MAAa,kBAAkB;AAC/B,MAAa,qBAAqB;;AAGlC,MAAa,2BAA2B;;;;;;;AAQxC,MAAa,qBAAqB;AAClC,MAAa,oBAAoB;AACjC,MAAa,6BAA6B;AAC1C,MAAa,wBAAwB;;AAGrC,MAAa,yBAA4C;CACvD;CACA;CACA;CACA;AACF;;;;;;;;AASA,MAAa,6CAA6B,IAAI,IAAI;CAAC;CAAK;CAAK;AAAG,CAAC;;;;;;;;;;;AAcjE,eAAsB,gBACpB,SACA,MACA,YAA0B,cAC1B,UAA8B,CAAC,GACZ;CACnB,IAAI,cAA+B;CACnC,IAAI;CACJ,KAAK,MAAM,gBAAgB,wBACzB,IAAI;EACF,MAAM,WAAW,MAAM,UAAU,GAAG,eAAe,WAAW,IAAI;EAClE,IAAI,CAAC,2BAA2B,IAAI,SAAS,MAAM,GAAG,OAAO;EAC7D,cAAc;CAChB,SAAS,OAAO;EACd,mBAAmB;EAGnB,IAAI,cAAc,OAAO,GAAG;GAC1B,MAAM,EAAE,4BAA4B,MAAM,OAAO,uBAAc,CAAA,MAAA,MAAA,EAAA,CAAA;GAC/D,IAAI,wBAAwB,KAAK,GAAG,MAAM;EAC5C;CAEF;CAEF,IAAI,aAAa,OAAO;CACxB,IAAI,qBAAqB,KAAA,GAAW,MAAM;CAC1C,MAAM,IAAI,MAAM,0BAA0B;AAC5C;;;;;;;;;;;;;;;;AAiBA,MAAa,uBAAuB;;;;;;;;;;AAWpC,MAAa,oBAAoB;;;;;;;;;;;;;;;;AAiBjC,IAAI;;AAGJ,SAAgB,sBAAsB,SAAmC;CACvE,qBAAqB;AACvB;;AAGA,SAAgB,oBAA4B;CAC1C,OAAO,sBAAA;AACT;;AAGA,SAAgB,yBAAyB,UAAU,kBAAkB,GAAW;CAC9E,OAAO,gGAAgG,QAAQ;AACjH;;;;;;;;AASA,SAAgB,qBAAqB,UAAU,kBAAkB,GAAG,WAAW,gBAAwB;CACrG,OAAO,eAAe,QAAQ,GAAG;AACnC;;;;;;;;;AAUA,MAAa,eAAe"}
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { a as AGY_PLATFORM_ENUM, g as resolveAgyClientCredentials, h as getAgyBootstrapUserAgent, i as AGY_IDE_TYPE, l as OAUTH_TOKEN_URL, p as currentAgyVersion, r as AGY_ENDPOINT_FALLBACKS, u as OAUTH_USERINFO_URL } from "./constants-CmA9wXNe.mjs";
3
- import { o as proxiedFetch } from "./proxy-BbuQX5U9.mjs";
2
+ import { a as AGY_PLATFORM_ENUM, g as resolveAgyClientCredentials, h as getAgyBootstrapUserAgent, i as AGY_IDE_TYPE, l as OAUTH_TOKEN_URL, p as currentAgyVersion, r as AGY_ENDPOINT_FALLBACKS, u as OAUTH_USERINFO_URL } from "./constants-AdsD4sKh.mjs";
3
+ import { o as proxiedFetch } from "./proxy-B7VnbAfO.mjs";
4
4
  import { n as calculateTokenExpiry } from "./auth-DPPVFwio.mjs";
5
5
  import { createHash, randomBytes } from "node:crypto";
6
6
  //#region src/oauth/pkce.ts
@@ -256,4 +256,4 @@ async function exchangeAntigravity(code, state, redirectUri, expectedVerifier, r
256
256
  //#endregion
257
257
  export { generatePkcePair as i, exchange_exports as n, encodeState as r, exchangeAntigravity as t };
258
258
 
259
- //# sourceMappingURL=exchange-C-tndv7j.mjs.map
259
+ //# sourceMappingURL=exchange-B_rHhU8K.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"exchange-C-tndv7j.mjs","names":["record"],"sources":["../src/oauth/pkce.ts","../src/oauth/exchange.ts"],"sourcesContent":["/** Minimal PKCE (RFC 7636) with S256, hand-rolled on node:crypto (no extra dependency). */\n\nimport { createHash, randomBytes } from 'node:crypto'\n\nfunction base64url(input: Buffer): string {\n return input.toString('base64url')\n}\n\n/** Generate a PKCE verifier (43-char URL-safe random) and its S256 challenge. */\nexport function generatePkcePair(): { verifier: string; challenge: string } {\n const verifier = base64url(randomBytes(32))\n const challenge = base64url(createHash('sha256').update(verifier).digest())\n return { verifier, challenge }\n}\n\n/** Encode an object into a URL-safe base64 string (used for the OAuth state). */\nexport function encodeState(payload: unknown): string {\n return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url')\n}\n\n/** Decode a URL-safe base64 OAuth state back into its structured representation. */\nexport function decodeState<T>(state: string): T {\n const normalized = state.replace(/-/g, '+').replace(/_/g, '/')\n const padded = normalized.padEnd(\n normalized.length + (((4 - (normalized.length % 4)) % 4)),\n '=',\n )\n const json = Buffer.from(padded, 'base64').toString('utf8')\n return JSON.parse(json) as T\n}\n","/**\n * Exchange an authorization code for agy tokens, then resolve identity and\n * project via the Code Assist backend (userinfo + loadCodeAssist, multi-endpoint\n * fallback, best-effort).\n */\n\nimport type { AccountRouting, TokenExchangeFailure, TokenExchangeResult } from '../types.ts'\nimport { calculateTokenExpiry } from './auth.ts'\nimport {\n AGY_ENDPOINT_FALLBACKS,\n AGY_IDE_TYPE,\n AGY_PLATFORM_ENUM,\n OAUTH_TOKEN_URL,\n OAUTH_USERINFO_URL,\n currentAgyVersion,\n getAgyBootstrapUserAgent,\n resolveAgyClientCredentials,\n} from './constants.ts'\nimport { decodeState } from './pkce.ts'\nimport { proxiedFetch } from '../proxy.ts'\n\nconst FETCH_TIMEOUT_MS = 10_000\n\ninterface OAuthState {\n verifier: string\n projectId?: string\n}\n\ninterface TokenPayload {\n access_token: string\n expires_in?: number\n refresh_token?: string\n}\n\ninterface UserInfo {\n email?: string\n}\n\nasync function fetchWithTimeout(\n url: string,\n options: RequestInit,\n timeoutMs = FETCH_TIMEOUT_MS,\n proxyUrl?: string,\n): Promise<Response> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await proxiedFetch(url, { ...options, signal: controller.signal }, { proxyUrl })\n } finally {\n clearTimeout(timeout)\n }\n}\n\n/** Bootstrap calls take the same routing as any other account-scoped request. */\nexport type AgyBootstrapOptions = AccountRouting\n\ninterface LoadCodeAssistData {\n cloudaicompanionProject?: unknown\n subscriptionInfo?: unknown\n}\n\n/**\n * Build the metadata payload shared by loadCodeAssist and onboardUser.\n *\n * This is the `ClientMetadata` BODY message (`docs/official-identity.json`), and\n * it replaces the `Client-Metadata` header this client used to send in addition:\n * neither official binary contains that header name, while `LoadCodeAssistRequest.metadata`\n * and `OnboardUserRequest.metadata` are both typed\n * `google.internal.cloud.code.v1internal.ClientMetadata` in the official\n * descriptor.\n *\n * The old \"send `ideType` only\" rule was a misreading of a real measurement: the\n * live `INVALID_ARGUMENT` was on the VALUE `\"MACOS\"`, which is not in the\n * `Platform` enum (`DARWIN_AMD64 | DARWIN_ARM64 | LINUX_AMD64 | LINUX_ARM64 |\n * WINDOWS_AMD64`). A bad enum value is not a forbidden field. `AGY_PLATFORM_ENUM`\n * carries a valid one.\n */\nexport function bootstrapMetadata(): Record<string, string> {\n return {\n ideType: AGY_IDE_TYPE,\n ideVersion: currentAgyVersion(),\n platform: AGY_PLATFORM_ENUM,\n }\n}\n\nfunction extractProjectId(data: LoadCodeAssistData): string {\n const project = data.cloudaicompanionProject\n if (typeof project === 'string' && project) return project\n const record = project as { id?: string } | undefined\n return record && typeof record.id === 'string' && record.id ? record.id : ''\n}\n\n/**\n * Extract the subscription tier id used for onboardUser, mirroring OmniRoute's\n * codeAssistSubscription: paid → current → default allowed → legacy-tier.\n */\nexport function extractOnboardTierId(subscriptionInfo: unknown): string {\n const subscription = (subscriptionInfo ?? {}) as Record<string, unknown>\n const tierOf = (value: unknown, field: 'name' | 'id'): string | null => {\n const record = value as Record<string, unknown> | undefined\n const picked = record?.[field]\n return typeof picked === 'string' && picked.trim() ? picked.trim() : null\n }\n\n const paidId = tierOf(subscription.paidTier, 'id')\n if (paidId) return paidId\n\n const ineligible = Array.isArray(subscription.ineligibleTiers) && subscription.ineligibleTiers.length > 0\n if (!ineligible) {\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n }\n\n if (Array.isArray(subscription.allowedTiers)) {\n for (const tierValue of subscription.allowedTiers) {\n const tier = tierValue as Record<string, unknown>\n if (tier.isDefault) {\n const defaultId = tierOf(tier, 'id')\n if (defaultId) return defaultId\n }\n }\n }\n\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n return 'legacy-tier'\n}\n\n/** Resolve project id + tier id via loadCodeAssist across fallback endpoints. */\nexport async function loadCodeAssist(\n accessToken: string,\n options: AgyBootstrapOptions = {},\n): Promise<{ projectId: string; tierId: string }> {\n const errors: string[] = []\n const loadHeaders: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n }\n\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const url = `${baseEndpoint}/v1internal:loadCodeAssist`\n const response = await fetchWithTimeout(url, {\n method: 'POST',\n headers: loadHeaders,\n body: JSON.stringify({ metadata: bootstrapMetadata() }),\n }, FETCH_TIMEOUT_MS, options.proxyUrl)\n\n if (!response.ok) {\n const message = await response.text().catch(() => '')\n errors.push(`loadCodeAssist ${response.status} at ${baseEndpoint}${message ? `: ${message}` : ''}`)\n continue\n }\n\n const data = (await response.json()) as LoadCodeAssistData\n const projectId = extractProjectId(data)\n if (projectId) {\n return { projectId, tierId: extractOnboardTierId(data.subscriptionInfo) }\n }\n errors.push(`loadCodeAssist missing project id at ${baseEndpoint}`)\n } catch (error) {\n errors.push(\n `loadCodeAssist error at ${baseEndpoint}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n return { projectId: '', tierId: 'legacy-tier' }\n}\n\n/**\n * Onboard a new account (no Cloud Code project yet): POST onboardUser with the\n * tier id, then retry loadCodeAssist. Mirrors OmniRoute's postExchange flow\n * (bounded inline onboarding, 10 attempts × 5s).\n */\nexport async function onboardAndDiscoverProject(\n accessToken: string,\n tierId: string,\n options: { maxAttempts?: number; retryDelayMs?: number } & AgyBootstrapOptions = {},\n): Promise<{ projectId: string; tierId: string }> {\n // Bounded onboarding: 3 attempts with jittered delay (3-7s). Fixed-rhythm\n // long retry loops read as scripted automation to the upstream (OmniRoute\n // #9939 ban-safety hardening capped 10x5s to 3x3-7s).\n const maxAttempts = options.maxAttempts ?? 3\n const retryDelayMs = options.retryDelayMs ?? 3000 + Math.floor(Math.random() * 4000)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n }\n const metadata = bootstrapMetadata()\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n const response = await fetchWithTimeout(`${baseEndpoint}/v1internal:onboardUser`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ tier_id: tierId, metadata }),\n }, FETCH_TIMEOUT_MS, options.proxyUrl)\n if (!response.ok) continue\n const result = (await response.json()) as { done?: boolean }\n if (result.done === true) {\n const discovered = await loadCodeAssist(accessToken, { proxyUrl: options.proxyUrl })\n if (discovered.projectId) return discovered\n }\n }\n } catch {\n // transient — retry\n }\n await new Promise((resolve) => setTimeout(resolve, retryDelayMs))\n }\n\n return { projectId: '', tierId }\n}\n\n/** Full bootstrap for a fresh account: discover the project, onboarding if needed. */\nexport async function bootstrapAccount(\n accessToken: string,\n options: { maxAttempts?: number; retryDelayMs?: number } & AgyBootstrapOptions = {},\n): Promise<{ projectId: string; tierId: string }> {\n const discovered = await loadCodeAssist(accessToken, options)\n if (discovered.projectId) return discovered\n return onboardAndDiscoverProject(accessToken, discovered.tierId, options)\n}\n\n/** Resolve the account's Cloud Code project id via loadCodeAssist across fallback endpoints. */\nexport async function fetchProjectID(accessToken: string, options: AgyBootstrapOptions = {}): Promise<string> {\n return (await loadCodeAssist(accessToken, options)).projectId\n}\n\n/**\n * Exchange the authorization code (from the loopback callback) for access/refresh\n * tokens, resolve the email, and discover the project id.\n * @param code - the `code` query parameter from the redirect.\n * @param state - the `state` query parameter (encodes verifier + projectId).\n * @param redirectUri - must match the authorize() redirectUri.\n */\nexport async function exchangeAntigravity(\n code: string,\n state: string,\n redirectUri: string,\n expectedVerifier?: string,\n routing: AccountRouting = {},\n): Promise<TokenExchangeResult> {\n try {\n const { verifier, projectId } = decodeState<OAuthState>(state)\n if (!verifier) {\n return { type: 'failed', error: 'Missing PKCE verifier in state' }\n }\n // The caller issued this authorization URL (the CLI holds the verifier;\n // the web entry stores it per state). A state from a different login — or\n // one fabricated by an attacker — must not be exchangeable into the store.\n if (expectedVerifier && verifier !== expectedVerifier) {\n return { type: 'failed', error: 'State does not match the issued authorization' }\n }\n\n const startTime = Date.now()\n const { clientId, clientSecret } = resolveAgyClientCredentials()\n const tokenResponse = await proxiedFetch(OAUTH_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n Accept: '*/*',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body: new URLSearchParams({\n client_id: clientId,\n client_secret: clientSecret,\n code,\n grant_type: 'authorization_code',\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n }, routing)\n\n if (!tokenResponse.ok) {\n const errorText = await tokenResponse.text()\n return { type: 'failed', error: errorText }\n }\n\n const tokenPayload = (await tokenResponse.json()) as TokenPayload\n\n const userInfoResponse = await proxiedFetch(`${OAUTH_USERINFO_URL}?alt=json`, {\n headers: {\n Authorization: `Bearer ${tokenPayload.access_token}`,\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n }, routing)\n const userInfo: UserInfo = userInfoResponse.ok\n ? ((await userInfoResponse.json()) as UserInfo)\n : {}\n\n const refreshToken = tokenPayload.refresh_token\n if (!refreshToken) {\n return { type: 'failed', error: 'Missing refresh token in response' }\n }\n\n const effectiveProjectId = projectId || (await bootstrapAccount(tokenPayload.access_token, routing)).projectId\n\n return {\n type: 'success',\n refresh: `${refreshToken}|${effectiveProjectId || ''}`,\n access: tokenPayload.access_token,\n expires: calculateTokenExpiry(startTime, tokenPayload.expires_in),\n email: userInfo.email,\n projectId: effectiveProjectId || '',\n clientId,\n }\n } catch (error) {\n const failure: TokenExchangeFailure = {\n type: 'failed',\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n return failure\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"exchange-B_rHhU8K.mjs","names":["record"],"sources":["../src/oauth/pkce.ts","../src/oauth/exchange.ts"],"sourcesContent":["/** Minimal PKCE (RFC 7636) with S256, hand-rolled on node:crypto (no extra dependency). */\n\nimport { createHash, randomBytes } from 'node:crypto'\n\nfunction base64url(input: Buffer): string {\n return input.toString('base64url')\n}\n\n/** Generate a PKCE verifier (43-char URL-safe random) and its S256 challenge. */\nexport function generatePkcePair(): { verifier: string; challenge: string } {\n const verifier = base64url(randomBytes(32))\n const challenge = base64url(createHash('sha256').update(verifier).digest())\n return { verifier, challenge }\n}\n\n/** Encode an object into a URL-safe base64 string (used for the OAuth state). */\nexport function encodeState(payload: unknown): string {\n return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url')\n}\n\n/** Decode a URL-safe base64 OAuth state back into its structured representation. */\nexport function decodeState<T>(state: string): T {\n const normalized = state.replace(/-/g, '+').replace(/_/g, '/')\n const padded = normalized.padEnd(\n normalized.length + (((4 - (normalized.length % 4)) % 4)),\n '=',\n )\n const json = Buffer.from(padded, 'base64').toString('utf8')\n return JSON.parse(json) as T\n}\n","/**\n * Exchange an authorization code for agy tokens, then resolve identity and\n * project via the Code Assist backend (userinfo + loadCodeAssist, multi-endpoint\n * fallback, best-effort).\n */\n\nimport type { AccountRouting, TokenExchangeFailure, TokenExchangeResult } from '../types.ts'\nimport { calculateTokenExpiry } from './auth.ts'\nimport {\n AGY_ENDPOINT_FALLBACKS,\n AGY_IDE_TYPE,\n AGY_PLATFORM_ENUM,\n OAUTH_TOKEN_URL,\n OAUTH_USERINFO_URL,\n currentAgyVersion,\n getAgyBootstrapUserAgent,\n resolveAgyClientCredentials,\n} from './constants.ts'\nimport { decodeState } from './pkce.ts'\nimport { proxiedFetch } from '../proxy.ts'\n\nconst FETCH_TIMEOUT_MS = 10_000\n\ninterface OAuthState {\n verifier: string\n projectId?: string\n}\n\ninterface TokenPayload {\n access_token: string\n expires_in?: number\n refresh_token?: string\n}\n\ninterface UserInfo {\n email?: string\n}\n\nasync function fetchWithTimeout(\n url: string,\n options: RequestInit,\n timeoutMs = FETCH_TIMEOUT_MS,\n proxyUrl?: string,\n): Promise<Response> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await proxiedFetch(url, { ...options, signal: controller.signal }, { proxyUrl })\n } finally {\n clearTimeout(timeout)\n }\n}\n\n/** Bootstrap calls take the same routing as any other account-scoped request. */\nexport type AgyBootstrapOptions = AccountRouting\n\ninterface LoadCodeAssistData {\n cloudaicompanionProject?: unknown\n subscriptionInfo?: unknown\n}\n\n/**\n * Build the metadata payload shared by loadCodeAssist and onboardUser.\n *\n * This is the `ClientMetadata` BODY message (`docs/official-identity.json`), and\n * it replaces the `Client-Metadata` header this client used to send in addition:\n * neither official binary contains that header name, while `LoadCodeAssistRequest.metadata`\n * and `OnboardUserRequest.metadata` are both typed\n * `google.internal.cloud.code.v1internal.ClientMetadata` in the official\n * descriptor.\n *\n * The old \"send `ideType` only\" rule was a misreading of a real measurement: the\n * live `INVALID_ARGUMENT` was on the VALUE `\"MACOS\"`, which is not in the\n * `Platform` enum (`DARWIN_AMD64 | DARWIN_ARM64 | LINUX_AMD64 | LINUX_ARM64 |\n * WINDOWS_AMD64`). A bad enum value is not a forbidden field. `AGY_PLATFORM_ENUM`\n * carries a valid one.\n */\nexport function bootstrapMetadata(): Record<string, string> {\n return {\n ideType: AGY_IDE_TYPE,\n ideVersion: currentAgyVersion(),\n platform: AGY_PLATFORM_ENUM,\n }\n}\n\nfunction extractProjectId(data: LoadCodeAssistData): string {\n const project = data.cloudaicompanionProject\n if (typeof project === 'string' && project) return project\n const record = project as { id?: string } | undefined\n return record && typeof record.id === 'string' && record.id ? record.id : ''\n}\n\n/**\n * Extract the subscription tier id used for onboardUser, mirroring OmniRoute's\n * codeAssistSubscription: paid → current → default allowed → legacy-tier.\n */\nexport function extractOnboardTierId(subscriptionInfo: unknown): string {\n const subscription = (subscriptionInfo ?? {}) as Record<string, unknown>\n const tierOf = (value: unknown, field: 'name' | 'id'): string | null => {\n const record = value as Record<string, unknown> | undefined\n const picked = record?.[field]\n return typeof picked === 'string' && picked.trim() ? picked.trim() : null\n }\n\n const paidId = tierOf(subscription.paidTier, 'id')\n if (paidId) return paidId\n\n const ineligible = Array.isArray(subscription.ineligibleTiers) && subscription.ineligibleTiers.length > 0\n if (!ineligible) {\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n }\n\n if (Array.isArray(subscription.allowedTiers)) {\n for (const tierValue of subscription.allowedTiers) {\n const tier = tierValue as Record<string, unknown>\n if (tier.isDefault) {\n const defaultId = tierOf(tier, 'id')\n if (defaultId) return defaultId\n }\n }\n }\n\n const currentId = tierOf(subscription.currentTier, 'id')\n if (currentId) return currentId\n return 'legacy-tier'\n}\n\n/** Resolve project id + tier id via loadCodeAssist across fallback endpoints. */\nexport async function loadCodeAssist(\n accessToken: string,\n options: AgyBootstrapOptions = {},\n): Promise<{ projectId: string; tierId: string }> {\n const errors: string[] = []\n const loadHeaders: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n }\n\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n try {\n const url = `${baseEndpoint}/v1internal:loadCodeAssist`\n const response = await fetchWithTimeout(url, {\n method: 'POST',\n headers: loadHeaders,\n body: JSON.stringify({ metadata: bootstrapMetadata() }),\n }, FETCH_TIMEOUT_MS, options.proxyUrl)\n\n if (!response.ok) {\n const message = await response.text().catch(() => '')\n errors.push(`loadCodeAssist ${response.status} at ${baseEndpoint}${message ? `: ${message}` : ''}`)\n continue\n }\n\n const data = (await response.json()) as LoadCodeAssistData\n const projectId = extractProjectId(data)\n if (projectId) {\n return { projectId, tierId: extractOnboardTierId(data.subscriptionInfo) }\n }\n errors.push(`loadCodeAssist missing project id at ${baseEndpoint}`)\n } catch (error) {\n errors.push(\n `loadCodeAssist error at ${baseEndpoint}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n\n return { projectId: '', tierId: 'legacy-tier' }\n}\n\n/**\n * Onboard a new account (no Cloud Code project yet): POST onboardUser with the\n * tier id, then retry loadCodeAssist. Mirrors OmniRoute's postExchange flow\n * (bounded inline onboarding, 10 attempts × 5s).\n */\nexport async function onboardAndDiscoverProject(\n accessToken: string,\n tierId: string,\n options: { maxAttempts?: number; retryDelayMs?: number } & AgyBootstrapOptions = {},\n): Promise<{ projectId: string; tierId: string }> {\n // Bounded onboarding: 3 attempts with jittered delay (3-7s). Fixed-rhythm\n // long retry loops read as scripted automation to the upstream (OmniRoute\n // #9939 ban-safety hardening capped 10x5s to 3x3-7s).\n const maxAttempts = options.maxAttempts ?? 3\n const retryDelayMs = options.retryDelayMs ?? 3000 + Math.floor(Math.random() * 4000)\n const headers: Record<string, string> = {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'User-Agent': getAgyBootstrapUserAgent(),\n }\n const metadata = bootstrapMetadata()\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n for (const baseEndpoint of AGY_ENDPOINT_FALLBACKS) {\n const response = await fetchWithTimeout(`${baseEndpoint}/v1internal:onboardUser`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ tier_id: tierId, metadata }),\n }, FETCH_TIMEOUT_MS, options.proxyUrl)\n if (!response.ok) continue\n const result = (await response.json()) as { done?: boolean }\n if (result.done === true) {\n const discovered = await loadCodeAssist(accessToken, { proxyUrl: options.proxyUrl })\n if (discovered.projectId) return discovered\n }\n }\n } catch {\n // transient — retry\n }\n await new Promise((resolve) => setTimeout(resolve, retryDelayMs))\n }\n\n return { projectId: '', tierId }\n}\n\n/** Full bootstrap for a fresh account: discover the project, onboarding if needed. */\nexport async function bootstrapAccount(\n accessToken: string,\n options: { maxAttempts?: number; retryDelayMs?: number } & AgyBootstrapOptions = {},\n): Promise<{ projectId: string; tierId: string }> {\n const discovered = await loadCodeAssist(accessToken, options)\n if (discovered.projectId) return discovered\n return onboardAndDiscoverProject(accessToken, discovered.tierId, options)\n}\n\n/** Resolve the account's Cloud Code project id via loadCodeAssist across fallback endpoints. */\nexport async function fetchProjectID(accessToken: string, options: AgyBootstrapOptions = {}): Promise<string> {\n return (await loadCodeAssist(accessToken, options)).projectId\n}\n\n/**\n * Exchange the authorization code (from the loopback callback) for access/refresh\n * tokens, resolve the email, and discover the project id.\n * @param code - the `code` query parameter from the redirect.\n * @param state - the `state` query parameter (encodes verifier + projectId).\n * @param redirectUri - must match the authorize() redirectUri.\n */\nexport async function exchangeAntigravity(\n code: string,\n state: string,\n redirectUri: string,\n expectedVerifier?: string,\n routing: AccountRouting = {},\n): Promise<TokenExchangeResult> {\n try {\n const { verifier, projectId } = decodeState<OAuthState>(state)\n if (!verifier) {\n return { type: 'failed', error: 'Missing PKCE verifier in state' }\n }\n // The caller issued this authorization URL (the CLI holds the verifier;\n // the web entry stores it per state). A state from a different login — or\n // one fabricated by an attacker — must not be exchangeable into the store.\n if (expectedVerifier && verifier !== expectedVerifier) {\n return { type: 'failed', error: 'State does not match the issued authorization' }\n }\n\n const startTime = Date.now()\n const { clientId, clientSecret } = resolveAgyClientCredentials()\n const tokenResponse = await proxiedFetch(OAUTH_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',\n Accept: '*/*',\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n body: new URLSearchParams({\n client_id: clientId,\n client_secret: clientSecret,\n code,\n grant_type: 'authorization_code',\n redirect_uri: redirectUri,\n code_verifier: verifier,\n }),\n }, routing)\n\n if (!tokenResponse.ok) {\n const errorText = await tokenResponse.text()\n return { type: 'failed', error: errorText }\n }\n\n const tokenPayload = (await tokenResponse.json()) as TokenPayload\n\n const userInfoResponse = await proxiedFetch(`${OAUTH_USERINFO_URL}?alt=json`, {\n headers: {\n Authorization: `Bearer ${tokenPayload.access_token}`,\n 'User-Agent': getAgyBootstrapUserAgent(),\n },\n }, routing)\n const userInfo: UserInfo = userInfoResponse.ok\n ? ((await userInfoResponse.json()) as UserInfo)\n : {}\n\n const refreshToken = tokenPayload.refresh_token\n if (!refreshToken) {\n return { type: 'failed', error: 'Missing refresh token in response' }\n }\n\n const effectiveProjectId = projectId || (await bootstrapAccount(tokenPayload.access_token, routing)).projectId\n\n return {\n type: 'success',\n refresh: `${refreshToken}|${effectiveProjectId || ''}`,\n access: tokenPayload.access_token,\n expires: calculateTokenExpiry(startTime, tokenPayload.expires_in),\n email: userInfo.email,\n projectId: effectiveProjectId || '',\n clientId,\n }\n } catch (error) {\n const failure: TokenExchangeFailure = {\n type: 'failed',\n error: error instanceof Error ? error.message : 'Unknown error',\n }\n return failure\n }\n}\n"],"mappings":";;;;;;;AAIA,SAAS,UAAU,OAAuB;CACxC,OAAO,MAAM,SAAS,WAAW;AACnC;;AAGA,SAAgB,mBAA4D;CAC1E,MAAM,WAAW,UAAU,YAAY,EAAE,CAAC;CAE1C,OAAO;EAAE;EAAU,WADD,UAAU,WAAW,QAAQ,CAAC,CAAC,OAAO,QAAQ,CAAC,CAAC,OAAO,CAC9C;CAAE;AAC/B;;AAGA,SAAgB,YAAY,SAA0B;CACpD,OAAO,OAAO,KAAK,KAAK,UAAU,OAAO,GAAG,MAAM,CAAC,CAAC,SAAS,WAAW;AAC1E;;AAGA,SAAgB,YAAe,OAAkB;CAC/C,MAAM,aAAa,MAAM,QAAQ,MAAM,GAAG,CAAC,CAAC,QAAQ,MAAM,GAAG;CAC7D,MAAM,SAAS,WAAW,OACxB,WAAW,UAAY,IAAK,WAAW,SAAS,KAAM,GACtD,GACF;CACA,MAAM,OAAO,OAAO,KAAK,QAAQ,QAAQ,CAAC,CAAC,SAAS,MAAM;CAC1D,OAAO,KAAK,MAAM,IAAI;AACxB;;;;;;;;;;;ACRA,MAAM,mBAAmB;AAiBzB,eAAe,iBACb,KACA,SACA,YAAY,kBACZ,UACmB;CACnB,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,UAAU,iBAAiB,WAAW,MAAM,GAAG,SAAS;CAC9D,IAAI;EACF,OAAO,MAAM,aAAa,KAAK;GAAE,GAAG;GAAS,QAAQ,WAAW;EAAO,GAAG,EAAE,SAAS,CAAC;CACxF,UAAU;EACR,aAAa,OAAO;CACtB;AACF;;;;;;;;;;;;;;;;;AA0BA,SAAgB,oBAA4C;CAC1D,OAAO;EACL,SAAS;EACT,YAAY,kBAAkB;EAC9B,UAAU;CACZ;AACF;AAEA,SAAS,iBAAiB,MAAkC;CAC1D,MAAM,UAAU,KAAK;CACrB,IAAI,OAAO,YAAY,YAAY,SAAS,OAAO;CACnD,MAAM,SAAS;CACf,OAAO,UAAU,OAAO,OAAO,OAAO,YAAY,OAAO,KAAK,OAAO,KAAK;AAC5E;;;;;AAMA,SAAgB,qBAAqB,kBAAmC;CACtE,MAAM,eAAgB,oBAAoB,CAAC;CAC3C,MAAM,UAAU,OAAgB,UAAwC;EAEtE,MAAM,SAASA,QAAS;EACxB,OAAO,OAAO,WAAW,YAAY,OAAO,KAAK,IAAI,OAAO,KAAK,IAAI;CACvE;CAEA,MAAM,SAAS,OAAO,aAAa,UAAU,IAAI;CACjD,IAAI,QAAQ,OAAO;CAGnB,IAAI,EADe,MAAM,QAAQ,aAAa,eAAe,KAAK,aAAa,gBAAgB,SAAS,IACvF;EACf,MAAM,YAAY,OAAO,aAAa,aAAa,IAAI;EACvD,IAAI,WAAW,OAAO;CACxB;CAEA,IAAI,MAAM,QAAQ,aAAa,YAAY,GACzC,KAAK,MAAM,aAAa,aAAa,cAAc;EACjD,MAAM,OAAO;EACb,IAAI,KAAK,WAAW;GAClB,MAAM,YAAY,OAAO,MAAM,IAAI;GACnC,IAAI,WAAW,OAAO;EACxB;CACF;CAGF,MAAM,YAAY,OAAO,aAAa,aAAa,IAAI;CACvD,IAAI,WAAW,OAAO;CACtB,OAAO;AACT;;AAGA,eAAsB,eACpB,aACA,UAA+B,CAAC,GACgB;CAChD,MAAM,SAAmB,CAAC;CAC1B,MAAM,cAAsC;EAC1C,eAAe,UAAU;EACzB,gBAAgB;EAChB,cAAc,yBAAyB;CACzC;CAEA,KAAK,MAAM,gBAAgB,wBACzB,IAAI;EAEF,MAAM,WAAW,MAAM,iBAAiB,GADzB,aAAa,6BACiB;GAC3C,QAAQ;GACR,SAAS;GACT,MAAM,KAAK,UAAU,EAAE,UAAU,kBAAkB,EAAE,CAAC;EACxD,GAAG,kBAAkB,QAAQ,QAAQ;EAErC,IAAI,CAAC,SAAS,IAAI;GAChB,MAAM,UAAU,MAAM,SAAS,KAAK,CAAC,CAAC,YAAY,EAAE;GACpD,OAAO,KAAK,kBAAkB,SAAS,OAAO,MAAM,eAAe,UAAU,KAAK,YAAY,IAAI;GAClG;EACF;EAEA,MAAM,OAAQ,MAAM,SAAS,KAAK;EAClC,MAAM,YAAY,iBAAiB,IAAI;EACvC,IAAI,WACF,OAAO;GAAE;GAAW,QAAQ,qBAAqB,KAAK,gBAAgB;EAAE;EAE1E,OAAO,KAAK,wCAAwC,cAAc;CACpE,SAAS,OAAO;EACd,OAAO,KACL,2BAA2B,aAAa,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GACnG;CACF;CAGF,OAAO;EAAE,WAAW;EAAI,QAAQ;CAAc;AAChD;;;;;;AAOA,eAAsB,0BACpB,aACA,QACA,UAAiF,CAAC,GAClC;CAIhD,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,eAAe,QAAQ,gBAAgB,MAAO,KAAK,MAAM,KAAK,OAAO,IAAI,GAAI;CACnF,MAAM,UAAkC;EACtC,eAAe,UAAU;EACzB,gBAAgB;EAChB,cAAc,yBAAyB;CACzC;CACA,MAAM,WAAW,kBAAkB;CAEnC,KAAK,IAAI,UAAU,GAAG,UAAU,aAAa,WAAW;EACtD,IAAI;GACF,KAAK,MAAM,gBAAgB,wBAAwB;IACjD,MAAM,WAAW,MAAM,iBAAiB,GAAG,aAAa,0BAA0B;KAChF,QAAQ;KACR;KACA,MAAM,KAAK,UAAU;MAAE,SAAS;MAAQ;KAAS,CAAC;IACpD,GAAG,kBAAkB,QAAQ,QAAQ;IACrC,IAAI,CAAC,SAAS,IAAI;IAElB,KAAI,MADkB,SAAS,KAAK,EAAA,CACzB,SAAS,MAAM;KACxB,MAAM,aAAa,MAAM,eAAe,aAAa,EAAE,UAAU,QAAQ,SAAS,CAAC;KACnF,IAAI,WAAW,WAAW,OAAO;IACnC;GACF;EACF,QAAQ,CAER;EACA,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,YAAY,CAAC;CAClE;CAEA,OAAO;EAAE,WAAW;EAAI;CAAO;AACjC;;AAGA,eAAsB,iBACpB,aACA,UAAiF,CAAC,GAClC;CAChD,MAAM,aAAa,MAAM,eAAe,aAAa,OAAO;CAC5D,IAAI,WAAW,WAAW,OAAO;CACjC,OAAO,0BAA0B,aAAa,WAAW,QAAQ,OAAO;AAC1E;;;;;;;;AAcA,eAAsB,oBACpB,MACA,OACA,aACA,kBACA,UAA0B,CAAC,GACG;CAC9B,IAAI;EACF,MAAM,EAAE,UAAU,cAAc,YAAwB,KAAK;EAC7D,IAAI,CAAC,UACH,OAAO;GAAE,MAAM;GAAU,OAAO;EAAiC;EAKnE,IAAI,oBAAoB,aAAa,kBACnC,OAAO;GAAE,MAAM;GAAU,OAAO;EAAgD;EAGlF,MAAM,YAAY,KAAK,IAAI;EAC3B,MAAM,EAAE,UAAU,iBAAiB,4BAA4B;EAC/D,MAAM,gBAAgB,MAAM,aAAa,iBAAiB;GACxD,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,QAAQ;IACR,cAAc,yBAAyB;GACzC;GACA,MAAM,IAAI,gBAAgB;IACxB,WAAW;IACX,eAAe;IACf;IACA,YAAY;IACZ,cAAc;IACd,eAAe;GACjB,CAAC;EACH,GAAG,OAAO;EAEV,IAAI,CAAC,cAAc,IAEjB,OAAO;GAAE,MAAM;GAAU,OAAO,MADR,cAAc,KAAK;EACD;EAG5C,MAAM,eAAgB,MAAM,cAAc,KAAK;EAE/C,MAAM,mBAAmB,MAAM,aAAa,GAAG,mBAAmB,YAAY,EAC5E,SAAS;GACP,eAAe,UAAU,aAAa;GACtC,cAAc,yBAAyB;EACzC,EACF,GAAG,OAAO;EACV,MAAM,WAAqB,iBAAiB,KACtC,MAAM,iBAAiB,KAAK,IAC9B,CAAC;EAEL,MAAM,eAAe,aAAa;EAClC,IAAI,CAAC,cACH,OAAO;GAAE,MAAM;GAAU,OAAO;EAAoC;EAGtE,MAAM,qBAAqB,cAAc,MAAM,iBAAiB,aAAa,cAAc,OAAO,EAAA,CAAG;EAErG,OAAO;GACL,MAAM;GACN,SAAS,GAAG,aAAa,GAAG,sBAAsB;GAClD,QAAQ,aAAa;GACrB,SAAS,qBAAqB,WAAW,aAAa,UAAU;GAChE,OAAO,SAAS;GAChB,WAAW,sBAAsB;GACjC;EACF;CACF,SAAS,OAAO;EAKd,OAAO;GAHL,MAAM;GACN,OAAO,iBAAiB,QAAQ,MAAM,UAAU;EAErC;CACf;AACF"}
@@ -1,6 +1,6 @@
1
- import { a as AGY_PLATFORM_ENUM, c as OAUTH_AUTHORIZE_URL, g as resolveAgyClientCredentials, h as getAgyBootstrapUserAgent, i as AGY_IDE_TYPE, o as AGY_SCOPES, p as currentAgyVersion, r as AGY_ENDPOINT_FALLBACKS } from "./constants-CmA9wXNe.mjs";
2
- import { i as normalizeProxyUrl, o as proxiedFetch } from "./proxy-BbuQX5U9.mjs";
3
- import { i as generatePkcePair, r as encodeState } from "./exchange-C-tndv7j.mjs";
1
+ import { a as AGY_PLATFORM_ENUM, c as OAUTH_AUTHORIZE_URL, g as resolveAgyClientCredentials, h as getAgyBootstrapUserAgent, i as AGY_IDE_TYPE, o as AGY_SCOPES, p as currentAgyVersion, r as AGY_ENDPOINT_FALLBACKS } from "./constants-AdsD4sKh.mjs";
2
+ import { i as normalizeProxyUrl, o as proxiedFetch } from "./proxy-B7VnbAfO.mjs";
3
+ import { i as generatePkcePair, r as encodeState } from "./exchange-B_rHhU8K.mjs";
4
4
  import { n as decodeCredentialBlob } from "./blob-D1e7_uT1.mjs";
5
5
  import { randomUUID } from "node:crypto";
6
6
  //#region src/oauth/authorize.ts
@@ -229,4 +229,4 @@ async function upsertImportedAccount(store, enriched, options = {}) {
229
229
  //#endregion
230
230
  export { upsertImportedAccount as n, authorizeAntigravity as r, importManySources as t };
231
231
 
232
- //# sourceMappingURL=import-SxPDjH3u.mjs.map
232
+ //# sourceMappingURL=import-BqyA9tzt.mjs.map