@kaitranntt/ccs 7.52.2 → 7.53.0-dev.2

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 (203) hide show
  1. package/README.md +9 -1
  2. package/config/base-claude.settings.json +3 -3
  3. package/config/base-codex.settings.json +1 -1
  4. package/config/base-llamacpp.settings.json +10 -0
  5. package/dist/api/services/index.d.ts +3 -1
  6. package/dist/api/services/index.d.ts.map +1 -1
  7. package/dist/api/services/index.js +10 -1
  8. package/dist/api/services/index.js.map +1 -1
  9. package/dist/api/services/profile-lifecycle-service.d.ts +24 -0
  10. package/dist/api/services/profile-lifecycle-service.d.ts.map +1 -0
  11. package/dist/api/services/profile-lifecycle-service.js +364 -0
  12. package/dist/api/services/profile-lifecycle-service.js.map +1 -0
  13. package/dist/api/services/profile-lifecycle-validation.d.ts +11 -0
  14. package/dist/api/services/profile-lifecycle-validation.d.ts.map +1 -0
  15. package/dist/api/services/profile-lifecycle-validation.js +87 -0
  16. package/dist/api/services/profile-lifecycle-validation.js.map +1 -0
  17. package/dist/api/services/profile-reader.js +1 -1
  18. package/dist/api/services/profile-reader.js.map +1 -1
  19. package/dist/api/services/profile-types.d.ts +66 -0
  20. package/dist/api/services/profile-types.d.ts.map +1 -1
  21. package/dist/api/services/profile-writer.d.ts.map +1 -1
  22. package/dist/api/services/profile-writer.js +23 -8
  23. package/dist/api/services/profile-writer.js.map +1 -1
  24. package/dist/auth/auth-commands.d.ts.map +1 -1
  25. package/dist/auth/auth-commands.js +4 -0
  26. package/dist/auth/auth-commands.js.map +1 -1
  27. package/dist/auth/commands/create-command.d.ts.map +1 -1
  28. package/dist/auth/commands/create-command.js +20 -7
  29. package/dist/auth/commands/create-command.js.map +1 -1
  30. package/dist/auth/commands/show-command.d.ts.map +1 -1
  31. package/dist/auth/commands/show-command.js +2 -0
  32. package/dist/auth/commands/show-command.js.map +1 -1
  33. package/dist/auth/commands/types.d.ts +2 -0
  34. package/dist/auth/commands/types.d.ts.map +1 -1
  35. package/dist/auth/commands/types.js +2 -0
  36. package/dist/auth/commands/types.js.map +1 -1
  37. package/dist/auth/profile-continuity-inheritance.d.ts.map +1 -1
  38. package/dist/auth/profile-continuity-inheritance.js +3 -1
  39. package/dist/auth/profile-continuity-inheritance.js.map +1 -1
  40. package/dist/auth/profile-detector.d.ts.map +1 -1
  41. package/dist/auth/profile-detector.js +1 -0
  42. package/dist/auth/profile-detector.js.map +1 -1
  43. package/dist/auth/profile-registry.d.ts +1 -0
  44. package/dist/auth/profile-registry.d.ts.map +1 -1
  45. package/dist/auth/profile-registry.js +3 -0
  46. package/dist/auth/profile-registry.js.map +1 -1
  47. package/dist/ccs.js +32 -4
  48. package/dist/ccs.js.map +1 -1
  49. package/dist/cliproxy/auth/auth-types.d.ts +3 -0
  50. package/dist/cliproxy/auth/auth-types.d.ts.map +1 -1
  51. package/dist/cliproxy/auth/auth-types.js +18 -1
  52. package/dist/cliproxy/auth/auth-types.js.map +1 -1
  53. package/dist/cliproxy/auth/gemini-token-refresh.d.ts.map +1 -1
  54. package/dist/cliproxy/auth/gemini-token-refresh.js +42 -14
  55. package/dist/cliproxy/auth/gemini-token-refresh.js.map +1 -1
  56. package/dist/cliproxy/auth/oauth-handler.d.ts +10 -0
  57. package/dist/cliproxy/auth/oauth-handler.d.ts.map +1 -1
  58. package/dist/cliproxy/auth/oauth-handler.js +60 -15
  59. package/dist/cliproxy/auth/oauth-handler.js.map +1 -1
  60. package/dist/cliproxy/binary-manager.d.ts.map +1 -1
  61. package/dist/cliproxy/binary-manager.js +13 -14
  62. package/dist/cliproxy/binary-manager.js.map +1 -1
  63. package/dist/cliproxy/config/thinking-config.d.ts.map +1 -1
  64. package/dist/cliproxy/config/thinking-config.js +9 -0
  65. package/dist/cliproxy/config/thinking-config.js.map +1 -1
  66. package/dist/cliproxy/model-catalog.d.ts.map +1 -1
  67. package/dist/cliproxy/model-catalog.js +14 -1
  68. package/dist/cliproxy/model-catalog.js.map +1 -1
  69. package/dist/cliproxy/quota-fetcher-claude.d.ts.map +1 -1
  70. package/dist/cliproxy/quota-fetcher-claude.js +51 -8
  71. package/dist/cliproxy/quota-fetcher-claude.js.map +1 -1
  72. package/dist/cliproxy/quota-fetcher-codex.d.ts.map +1 -1
  73. package/dist/cliproxy/quota-fetcher-codex.js +199 -63
  74. package/dist/cliproxy/quota-fetcher-codex.js.map +1 -1
  75. package/dist/cliproxy/quota-fetcher.d.ts +12 -0
  76. package/dist/cliproxy/quota-fetcher.d.ts.map +1 -1
  77. package/dist/cliproxy/quota-fetcher.js +328 -260
  78. package/dist/cliproxy/quota-fetcher.js.map +1 -1
  79. package/dist/cliproxy/quota-manager.d.ts +1 -1
  80. package/dist/cliproxy/quota-manager.d.ts.map +1 -1
  81. package/dist/cliproxy/quota-manager.js +9 -7
  82. package/dist/cliproxy/quota-manager.js.map +1 -1
  83. package/dist/cliproxy/quota-types.d.ts +18 -4
  84. package/dist/cliproxy/quota-types.d.ts.map +1 -1
  85. package/dist/cliproxy/thinking-validator.js +20 -0
  86. package/dist/cliproxy/thinking-validator.js.map +1 -1
  87. package/dist/cliproxy/tool-sanitization-proxy.d.ts.map +1 -1
  88. package/dist/cliproxy/tool-sanitization-proxy.js +86 -2
  89. package/dist/cliproxy/tool-sanitization-proxy.js.map +1 -1
  90. package/dist/commands/api-command.d.ts.map +1 -1
  91. package/dist/commands/api-command.js +299 -13
  92. package/dist/commands/api-command.js.map +1 -1
  93. package/dist/commands/copilot-command.d.ts.map +1 -1
  94. package/dist/commands/copilot-command.js +38 -0
  95. package/dist/commands/copilot-command.js.map +1 -1
  96. package/dist/commands/env-command.js +1 -1
  97. package/dist/commands/env-command.js.map +1 -1
  98. package/dist/commands/help-command.d.ts.map +1 -1
  99. package/dist/commands/help-command.js +7 -1
  100. package/dist/commands/help-command.js.map +1 -1
  101. package/dist/commands/sync-command.d.ts.map +1 -1
  102. package/dist/commands/sync-command.js +25 -0
  103. package/dist/commands/sync-command.js.map +1 -1
  104. package/dist/config/unified-config-types.d.ts +2 -0
  105. package/dist/config/unified-config-types.d.ts.map +1 -1
  106. package/dist/config/unified-config-types.js.map +1 -1
  107. package/dist/copilot/copilot-models.d.ts.map +1 -1
  108. package/dist/copilot/copilot-models.js +55 -13
  109. package/dist/copilot/copilot-models.js.map +1 -1
  110. package/dist/copilot/types.d.ts +10 -0
  111. package/dist/copilot/types.d.ts.map +1 -1
  112. package/dist/management/instance-manager.d.ts +9 -6
  113. package/dist/management/instance-manager.d.ts.map +1 -1
  114. package/dist/management/instance-manager.js +56 -34
  115. package/dist/management/instance-manager.js.map +1 -1
  116. package/dist/shared/provider-preset-catalog.d.ts +1 -1
  117. package/dist/shared/provider-preset-catalog.d.ts.map +1 -1
  118. package/dist/shared/provider-preset-catalog.js +30 -0
  119. package/dist/shared/provider-preset-catalog.js.map +1 -1
  120. package/dist/types/config.d.ts +2 -0
  121. package/dist/types/config.d.ts.map +1 -1
  122. package/dist/types/config.js.map +1 -1
  123. package/dist/ui/assets/accounts-PwWppkAw.js +1 -0
  124. package/dist/ui/assets/{alert-dialog-DPdKlUG9.js → alert-dialog-BKNsxsSg.js} +1 -1
  125. package/dist/ui/assets/api-C9f0vP-J.js +4 -0
  126. package/dist/ui/assets/auth-section-D0XQSyo0.js +1 -0
  127. package/dist/ui/assets/backups-section-Dxj8M3n7.js +1 -0
  128. package/dist/ui/assets/{checkbox-CcX8-GfD.js → checkbox-mjr1O0jA.js} +1 -1
  129. package/dist/ui/assets/cliproxy-BwRuqd53.js +3 -0
  130. package/dist/ui/assets/cliproxy-control-panel-VThtggLh.js +1 -0
  131. package/dist/ui/assets/{confirm-dialog-BZj0PYFs.js → confirm-dialog-i-Sfbmcm.js} +1 -1
  132. package/dist/ui/assets/copilot-Bp6z4OCx.js +3 -0
  133. package/dist/ui/assets/{cursor-rS1S0i_y.js → cursor-CJP1tf06.js} +1 -1
  134. package/dist/ui/assets/{droid-Dfc2QwbE.js → droid-R1Tbhk5J.js} +1 -1
  135. package/dist/ui/assets/{form-utils-Cn_Uld6y.js → form-utils-Bcoyqxpq.js} +1 -1
  136. package/dist/ui/assets/globalenv-section-TvE1onaY.js +1 -0
  137. package/dist/ui/assets/{health-B0WQPDXb.js → health-Cyjkkaf-.js} +1 -1
  138. package/dist/ui/assets/{icons-BYZM_9Gm.js → icons-D2eEmpHv.js} +1 -1
  139. package/dist/ui/assets/index-1DQih7xp.js +1 -0
  140. package/dist/ui/assets/index-BlXbW1dV.js +1 -0
  141. package/dist/ui/assets/index-BusjPRWX.css +1 -0
  142. package/dist/ui/assets/index-CcjPykyr.js +1 -0
  143. package/dist/ui/assets/{index-LHbr_5SB.js → index-DfbkdLOb.js} +1 -1
  144. package/dist/ui/assets/index-WhofYWgJ.js +47 -0
  145. package/dist/ui/assets/proxy-status-widget-O38AQ1k4.js +1 -0
  146. package/dist/ui/assets/{separator-DtcqgZIS.js → separator-B66kRzAj.js} +1 -1
  147. package/dist/ui/assets/shared-BG19Chhy.js +8 -0
  148. package/dist/ui/assets/{switch-BL5xZtnr.js → switch-IqCO_Age.js} +1 -1
  149. package/dist/ui/assets/{updates-B3HKUp7y.js → updates-_5C-OO-6.js} +1 -1
  150. package/dist/ui/index.html +4 -4
  151. package/dist/utils/api-key-validator.d.ts +2 -0
  152. package/dist/utils/api-key-validator.d.ts.map +1 -1
  153. package/dist/utils/api-key-validator.js +64 -1
  154. package/dist/utils/api-key-validator.js.map +1 -1
  155. package/dist/utils/delegation-validator.d.ts.map +1 -1
  156. package/dist/utils/delegation-validator.js +4 -3
  157. package/dist/utils/delegation-validator.js.map +1 -1
  158. package/dist/utils/error-codes.d.ts +1 -1
  159. package/dist/utils/error-codes.d.ts.map +1 -1
  160. package/dist/utils/error-codes.js +11 -6
  161. package/dist/utils/error-codes.js.map +1 -1
  162. package/dist/web-server/routes/account-routes.d.ts.map +1 -1
  163. package/dist/web-server/routes/account-routes.js +2 -1
  164. package/dist/web-server/routes/account-routes.js.map +1 -1
  165. package/dist/web-server/routes/cliproxy-stats-routes.d.ts +12 -0
  166. package/dist/web-server/routes/cliproxy-stats-routes.d.ts.map +1 -1
  167. package/dist/web-server/routes/cliproxy-stats-routes.js +22 -9
  168. package/dist/web-server/routes/cliproxy-stats-routes.js.map +1 -1
  169. package/dist/web-server/routes/profile-routes.d.ts.map +1 -1
  170. package/dist/web-server/routes/profile-routes.js +218 -18
  171. package/dist/web-server/routes/profile-routes.js.map +1 -1
  172. package/dist/web-server/routes/route-helpers.d.ts +1 -0
  173. package/dist/web-server/routes/route-helpers.d.ts.map +1 -1
  174. package/dist/web-server/routes/route-helpers.js +51 -13
  175. package/dist/web-server/routes/route-helpers.js.map +1 -1
  176. package/dist/web-server/routes/settings-routes.d.ts.map +1 -1
  177. package/dist/web-server/routes/settings-routes.js +3 -0
  178. package/dist/web-server/routes/settings-routes.js.map +1 -1
  179. package/dist/web-server/services/cliproxy-dashboard-install-service.d.ts +21 -0
  180. package/dist/web-server/services/cliproxy-dashboard-install-service.d.ts.map +1 -0
  181. package/dist/web-server/services/cliproxy-dashboard-install-service.js +51 -0
  182. package/dist/web-server/services/cliproxy-dashboard-install-service.js.map +1 -0
  183. package/lib/error-codes.ps1 +14 -4
  184. package/lib/error-codes.sh +10 -6
  185. package/lib/hooks/image-analyzer-transformer.cjs +51 -20
  186. package/package.json +2 -6
  187. package/dist/ui/assets/accounts-szAllF_0.js +0 -1
  188. package/dist/ui/assets/api-Bq7TnM0v.js +0 -1
  189. package/dist/ui/assets/auth-section-DYPWbNRj.js +0 -1
  190. package/dist/ui/assets/backups-section-gIWfCGmR.js +0 -1
  191. package/dist/ui/assets/cliproxy-SS8eRAX0.js +0 -3
  192. package/dist/ui/assets/cliproxy-control-panel-Bu0TtDft.js +0 -1
  193. package/dist/ui/assets/copilot-DnJj3frU.js +0 -3
  194. package/dist/ui/assets/globalenv-section-BrHb5lRq.js +0 -1
  195. package/dist/ui/assets/index-BFTIN2qO.js +0 -1
  196. package/dist/ui/assets/index-BVBXszJi.js +0 -1
  197. package/dist/ui/assets/index-DCQkhmoo.js +0 -47
  198. package/dist/ui/assets/index-DxKsP0Ke.js +0 -1
  199. package/dist/ui/assets/index-WBo504Wu.css +0 -1
  200. package/dist/ui/assets/proxy-status-widget-DPXgRGkB.js +0 -1
  201. package/dist/ui/assets/shared-DPJ_z23p.js +0 -8
  202. package/scripts/maintainability-baseline.js +0 -308
  203. package/scripts/maintainability-check.js +0 -163
@@ -35,14 +35,11 @@ const path = __importStar(require("node:path"));
35
35
  const config_generator_1 = require("./config-generator");
36
36
  const account_manager_1 = require("./account-manager");
37
37
  const auth_utils_1 = require("./auth-utils");
38
+ const proxy_target_resolver_1 = require("./proxy-target-resolver");
38
39
  /** Google Cloud Code API endpoints */
39
40
  const ANTIGRAVITY_API_BASE = 'https://cloudcode-pa.googleapis.com';
40
41
  const ANTIGRAVITY_API_VERSION = 'v1internal';
41
- /** Google OAuth token endpoint */
42
- const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
43
- /** Antigravity OAuth credentials (from CLIProxyAPIPlus - public in open-source code) */
44
- const ANTIGRAVITY_CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
45
- const ANTIGRAVITY_CLIENT_SECRET = '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';
42
+ const MANAGEMENT_API_TIMEOUT_MS = 5000;
46
43
  /** Headers for loadCodeAssist (matches CLIProxyAPI antigravity.go) */
47
44
  const LOADCODEASSIST_HEADERS = {
48
45
  'Content-Type': 'application/json',
@@ -55,61 +52,221 @@ const FETCHMODELS_HEADERS = {
55
52
  'Content-Type': 'application/json',
56
53
  'User-Agent': 'antigravity/1.104.0 darwin/arm64',
57
54
  };
58
- /**
59
- * Refresh access token using refresh_token via Google OAuth
60
- * This allows CCS to get fresh tokens independently of CLIProxyAPI
61
- */
62
- async function refreshAccessToken(refreshToken, verbose = false) {
63
- if (verbose)
64
- console.error('[i] Refreshing access token...');
55
+ function safeParseJson(bodyText) {
56
+ try {
57
+ return JSON.parse(bodyText);
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ function normalizeErrorDetail(bodyText) {
64
+ const normalized = bodyText.trim();
65
+ if (!normalized) {
66
+ return undefined;
67
+ }
68
+ if (normalized.length <= 400) {
69
+ return normalized;
70
+ }
71
+ return `${normalized.slice(0, 397)}...`;
72
+ }
73
+ function buildAntigravityFailure(status, bodyText) {
74
+ const detail = normalizeErrorDetail(bodyText || '');
75
+ if (status === 401) {
76
+ return {
77
+ httpStatus: 401,
78
+ error: 'Token expired or invalid',
79
+ errorCode: 'reauth_required',
80
+ actionHint: 'Re-authenticate this account. If CLIProxy is running, retry after the proxy finishes refreshing the token.',
81
+ needsReauth: true,
82
+ errorDetail: detail,
83
+ };
84
+ }
85
+ if (status === 403) {
86
+ return {
87
+ httpStatus: 403,
88
+ error: 'Access forbidden',
89
+ errorCode: 'quota_api_forbidden',
90
+ actionHint: 'This account does not have Gemini Code Assist quota access.',
91
+ isForbidden: true,
92
+ errorDetail: detail,
93
+ };
94
+ }
95
+ if (status === 429) {
96
+ return {
97
+ httpStatus: 429,
98
+ error: 'Rate limited - try again later',
99
+ errorCode: 'rate_limited',
100
+ actionHint: 'Retry later. This looks temporary.',
101
+ retryable: true,
102
+ errorDetail: detail,
103
+ };
104
+ }
105
+ if (status === 408) {
106
+ return {
107
+ httpStatus: 408,
108
+ error: 'Request timeout',
109
+ errorCode: 'network_timeout',
110
+ actionHint: 'Retry later. This looks temporary.',
111
+ retryable: true,
112
+ errorDetail: detail,
113
+ };
114
+ }
115
+ if (typeof status === 'number' && status >= 500) {
116
+ return {
117
+ httpStatus: status,
118
+ error: `API error: ${status}`,
119
+ errorCode: 'provider_unavailable',
120
+ actionHint: 'Retry later. The provider appears unavailable.',
121
+ retryable: true,
122
+ errorDetail: detail,
123
+ };
124
+ }
125
+ if (typeof status === 'number' && status >= 400) {
126
+ return {
127
+ httpStatus: status,
128
+ error: `API error: ${status}`,
129
+ errorCode: 'quota_request_failed',
130
+ errorDetail: detail,
131
+ };
132
+ }
133
+ return {
134
+ error: 'Quota request failed',
135
+ errorCode: 'quota_request_failed',
136
+ errorDetail: detail,
137
+ };
138
+ }
139
+ async function readManagedResponse(response, viaManagement) {
140
+ const bodyText = await response.text();
141
+ return {
142
+ status: response.status,
143
+ bodyText,
144
+ json: safeParseJson(bodyText),
145
+ viaManagement,
146
+ };
147
+ }
148
+ function isAntigravityAuthFileForAccount(file, accountId) {
149
+ const provider = (file.provider || file.type || '').trim().toLowerCase();
150
+ if (provider !== 'antigravity' && provider !== 'agy') {
151
+ return false;
152
+ }
153
+ const normalizedAccount = accountId.trim().toLowerCase();
154
+ const normalizedEmail = file.email?.trim().toLowerCase();
155
+ if (normalizedEmail && normalizedEmail === normalizedAccount) {
156
+ return true;
157
+ }
158
+ const normalizedName = file.name?.trim().toLowerCase();
159
+ if (!normalizedName) {
160
+ return false;
161
+ }
162
+ const sanitizedAccount = (0, auth_utils_1.sanitizeEmail)(accountId).toLowerCase();
163
+ return (normalizedName === `antigravity-${sanitizedAccount}.json` ||
164
+ normalizedName === `agy-${sanitizedAccount}.json`);
165
+ }
166
+ async function findManagedAntigravityAuthIndex(accountId) {
167
+ const target = (0, proxy_target_resolver_1.getProxyTarget)();
65
168
  const controller = new AbortController();
66
- const timeoutId = setTimeout(() => controller.abort(), 10000);
169
+ const timeoutId = setTimeout(() => controller.abort(), MANAGEMENT_API_TIMEOUT_MS);
67
170
  try {
68
- const response = await fetch(GOOGLE_TOKEN_URL, {
171
+ const response = await fetch((0, proxy_target_resolver_1.buildProxyUrl)(target, '/v0/management/auth-files'), {
172
+ signal: controller.signal,
173
+ headers: (0, proxy_target_resolver_1.buildManagementHeaders)(target),
174
+ });
175
+ clearTimeout(timeoutId);
176
+ if (!response.ok) {
177
+ return null;
178
+ }
179
+ const data = (await response.json());
180
+ const match = data.files?.find((file) => isAntigravityAuthFileForAccount(file, accountId));
181
+ return match?.auth_index ?? null;
182
+ }
183
+ catch {
184
+ clearTimeout(timeoutId);
185
+ return null;
186
+ }
187
+ }
188
+ async function performManagedAntigravityRequest(accountId, url, headers, body) {
189
+ const authIndex = await findManagedAntigravityAuthIndex(accountId);
190
+ if (authIndex === null || authIndex === undefined) {
191
+ return null;
192
+ }
193
+ const target = (0, proxy_target_resolver_1.getProxyTarget)();
194
+ const controller = new AbortController();
195
+ const timeoutId = setTimeout(() => controller.abort(), MANAGEMENT_API_TIMEOUT_MS);
196
+ try {
197
+ const response = await fetch((0, proxy_target_resolver_1.buildProxyUrl)(target, '/v0/management/api-call'), {
198
+ method: 'POST',
199
+ signal: controller.signal,
200
+ headers: (0, proxy_target_resolver_1.buildManagementHeaders)(target, {
201
+ 'Content-Type': 'application/json',
202
+ }),
203
+ body: JSON.stringify({
204
+ auth_index: authIndex,
205
+ method: 'POST',
206
+ url,
207
+ header: {
208
+ ...headers,
209
+ Authorization: 'Bearer $TOKEN$',
210
+ },
211
+ data: body,
212
+ }),
213
+ });
214
+ clearTimeout(timeoutId);
215
+ if (!response.ok) {
216
+ return null;
217
+ }
218
+ const apiResponse = (await response.json());
219
+ const bodyText = typeof apiResponse.body === 'string' ? apiResponse.body : '';
220
+ return {
221
+ status: typeof apiResponse.status_code === 'number' ? apiResponse.status_code : 500,
222
+ bodyText,
223
+ json: safeParseJson(bodyText),
224
+ viaManagement: true,
225
+ };
226
+ }
227
+ catch {
228
+ clearTimeout(timeoutId);
229
+ return null;
230
+ }
231
+ }
232
+ async function performAntigravityRequest(accountId, accessToken, url, headers, body) {
233
+ const controller = new AbortController();
234
+ const timeoutId = setTimeout(() => controller.abort(), MANAGEMENT_API_TIMEOUT_MS);
235
+ try {
236
+ const response = await fetch(url, {
69
237
  method: 'POST',
70
238
  signal: controller.signal,
71
239
  headers: {
72
- 'Content-Type': 'application/x-www-form-urlencoded',
240
+ ...headers,
241
+ Authorization: `Bearer ${accessToken}`,
73
242
  },
74
- body: new URLSearchParams({
75
- grant_type: 'refresh_token',
76
- refresh_token: refreshToken,
77
- client_id: ANTIGRAVITY_CLIENT_ID,
78
- client_secret: ANTIGRAVITY_CLIENT_SECRET,
79
- }).toString(),
243
+ body,
80
244
  });
81
245
  clearTimeout(timeoutId);
82
- if (verbose)
83
- console.error(`[i] Token refresh status: ${response.status}`);
84
- const data = (await response.json());
85
- if (!response.ok || data.error) {
86
- const error = data.error_description || data.error || `OAuth error: ${response.status}`;
87
- if (verbose)
88
- console.error(`[!] Token refresh failed: ${error}`);
89
- return {
90
- accessToken: null,
91
- error,
92
- };
246
+ const directResult = await readManagedResponse(response, false);
247
+ if (directResult.status !== 401) {
248
+ return directResult;
93
249
  }
94
- if (!data.access_token) {
95
- if (verbose)
96
- console.error('[!] Token refresh failed: No access_token in response');
97
- return { accessToken: null, error: 'No access_token in response' };
98
- }
99
- if (verbose)
100
- console.error('[i] Token refresh: success');
101
- return { accessToken: data.access_token };
250
+ const managedResult = await performManagedAntigravityRequest(accountId, url, headers, body);
251
+ return managedResult ?? directResult;
102
252
  }
103
253
  catch (err) {
104
254
  clearTimeout(timeoutId);
105
- const errorMsg = err instanceof Error && err.name === 'AbortError'
106
- ? 'Token refresh timeout'
107
- : err instanceof Error
108
- ? err.message
109
- : 'Unknown error';
110
- if (verbose)
111
- console.error(`[!] Token refresh failed: ${errorMsg}`);
112
- return { accessToken: null, error: errorMsg };
255
+ if (err instanceof Error && err.name === 'AbortError') {
256
+ return {
257
+ status: 408,
258
+ bodyText: '',
259
+ json: null,
260
+ viaManagement: false,
261
+ };
262
+ }
263
+ const message = err instanceof Error ? err.message : 'Unknown error';
264
+ return {
265
+ status: 503,
266
+ bodyText: message,
267
+ json: null,
268
+ viaManagement: false,
269
+ };
113
270
  }
114
271
  }
115
272
  /**
@@ -198,173 +355,111 @@ function mapTierString(tierStr) {
198
355
  * Get project ID and tier via loadCodeAssist endpoint
199
356
  * Uses paidTier.id for accurate tier detection (g1-ultra-tier, g1-pro-tier)
200
357
  */
201
- async function getProjectId(accessToken) {
358
+ async function getProjectId(accountId, accessToken) {
202
359
  const url = `${ANTIGRAVITY_API_BASE}/${ANTIGRAVITY_API_VERSION}:loadCodeAssist`;
203
- const controller = new AbortController();
204
- const timeoutId = setTimeout(() => controller.abort(), 5000);
205
- try {
206
- const response = await fetch(url, {
207
- method: 'POST',
208
- signal: controller.signal,
209
- headers: {
210
- ...LOADCODEASSIST_HEADERS,
211
- Authorization: `Bearer ${accessToken}`,
212
- },
213
- body: JSON.stringify({
214
- metadata: {
215
- ideType: 'IDE_UNSPECIFIED',
216
- platform: 'PLATFORM_UNSPECIFIED',
217
- pluginType: 'GEMINI',
218
- },
219
- }),
220
- });
221
- clearTimeout(timeoutId);
222
- if (!response.ok) {
223
- // Return specific error based on status
224
- if (response.status === 401) {
225
- return { projectId: null, error: 'Token expired or invalid' };
226
- }
227
- if (response.status === 403) {
228
- return { projectId: null, error: 'Access forbidden' };
229
- }
230
- return { projectId: null, error: `API error: ${response.status}` };
231
- }
232
- const data = (await response.json());
233
- // Extract project ID from response
234
- let projectId;
235
- if (typeof data.cloudaicompanionProject === 'string') {
236
- projectId = data.cloudaicompanionProject;
237
- }
238
- else if (typeof data.cloudaicompanionProject === 'object') {
239
- projectId = data.cloudaicompanionProject?.id;
240
- }
241
- if (!projectId?.trim()) {
242
- // Account authenticated but not provisioned - user needs to sign in via Antigravity app
243
- return {
244
- projectId: null,
245
- error: 'Sign in to Antigravity app to activate quota.',
246
- isUnprovisioned: true,
247
- };
248
- }
249
- // Extract tier - paidTier reflects actual subscription status, takes priority
250
- // API returns: paidTier.id = "g1-ultra-tier" or "g1-pro-tier"
251
- // allowedTiers/currentTier often return "standard-tier" which is not useful
252
- const tierStr = data.paidTier?.id || data.currentTier?.id;
253
- const tier = mapTierString(tierStr);
254
- return { projectId: projectId.trim(), tier };
360
+ const body = JSON.stringify({
361
+ metadata: {
362
+ ideType: 'IDE_UNSPECIFIED',
363
+ platform: 'PLATFORM_UNSPECIFIED',
364
+ pluginType: 'GEMINI',
365
+ },
366
+ });
367
+ const response = await performAntigravityRequest(accountId, accessToken, url, LOADCODEASSIST_HEADERS, body);
368
+ if (response.status < 200 || response.status >= 300) {
369
+ return {
370
+ projectId: null,
371
+ ...buildAntigravityFailure(response.status, response.bodyText),
372
+ };
255
373
  }
256
- catch (err) {
257
- clearTimeout(timeoutId);
258
- if (err instanceof Error && err.name === 'AbortError') {
259
- return { projectId: null, error: 'Request timeout' };
260
- }
261
- return { projectId: null, error: err instanceof Error ? err.message : 'Unknown error' };
374
+ const data = response.json;
375
+ if (!data) {
376
+ return {
377
+ projectId: null,
378
+ error: 'Invalid quota response from provider',
379
+ errorCode: 'provider_unavailable',
380
+ retryable: true,
381
+ };
382
+ }
383
+ // Extract project ID from response
384
+ let projectId;
385
+ if (typeof data.cloudaicompanionProject === 'string') {
386
+ projectId = data.cloudaicompanionProject;
387
+ }
388
+ else if (typeof data.cloudaicompanionProject === 'object') {
389
+ projectId = data.cloudaicompanionProject?.id;
262
390
  }
391
+ if (!projectId?.trim()) {
392
+ return {
393
+ projectId: null,
394
+ error: 'Sign in to Antigravity app to activate quota.',
395
+ errorCode: 'account_unprovisioned',
396
+ actionHint: 'Complete sign-in in the Antigravity app, then retry quota refresh.',
397
+ isUnprovisioned: true,
398
+ };
399
+ }
400
+ // Extract tier - paidTier reflects actual subscription status, takes priority
401
+ const tierStr = data.paidTier?.id || data.currentTier?.id;
402
+ const tier = mapTierString(tierStr);
403
+ return { projectId: projectId.trim(), tier };
263
404
  }
264
405
  /**
265
406
  * Fetch available models with quota info
266
407
  * Note: projectId is kept for potential future use but not sent in body
267
408
  * (CLIProxyAPI sends empty {} body for this endpoint)
268
409
  */
269
- async function fetchAvailableModels(accessToken, _projectId) {
410
+ async function fetchAvailableModels(accountId, accessToken, _projectId) {
270
411
  const url = `${ANTIGRAVITY_API_BASE}/${ANTIGRAVITY_API_VERSION}:fetchAvailableModels`;
271
- const controller = new AbortController();
272
- const timeoutId = setTimeout(() => controller.abort(), 5000);
273
- try {
274
- // Match CLIProxyAPI exactly: empty body, minimal headers
275
- const response = await fetch(url, {
276
- method: 'POST',
277
- signal: controller.signal,
278
- headers: {
279
- ...FETCHMODELS_HEADERS,
280
- Authorization: `Bearer ${accessToken}`,
281
- },
282
- body: JSON.stringify({}),
283
- });
284
- clearTimeout(timeoutId);
285
- if (response.status === 403) {
286
- // 403 = account lacks Gemini Code Assist access (not same as quota exhausted)
287
- // Keep success=false with isForbidden flag for UI to show distinct "403" badge
288
- return {
289
- success: false,
290
- models: [],
291
- lastUpdated: Date.now(),
292
- isForbidden: true,
293
- error: '403 Forbidden - No Gemini Code Assist access',
294
- };
295
- }
296
- if (response.status === 401) {
297
- return {
298
- success: false,
299
- models: [],
300
- lastUpdated: Date.now(),
301
- error: 'Access token expired or invalid',
302
- };
303
- }
304
- if (response.status === 429) {
305
- return {
306
- success: false,
307
- models: [],
308
- lastUpdated: Date.now(),
309
- error: 'Rate limited - try again later',
310
- };
311
- }
312
- if (!response.ok) {
313
- return {
314
- success: false,
315
- models: [],
316
- lastUpdated: Date.now(),
317
- error: `API error: ${response.status}`,
318
- };
319
- }
320
- const data = (await response.json());
321
- const models = [];
322
- if (data.models && typeof data.models === 'object') {
323
- for (const [modelId, modelData] of Object.entries(data.models)) {
324
- const quotaInfo = modelData.quotaInfo || modelData.quota_info;
325
- if (!quotaInfo)
326
- continue;
327
- // Extract remaining fraction (0-1 range)
328
- const remaining = quotaInfo.remainingFraction ?? quotaInfo.remaining_fraction ?? quotaInfo.remaining;
329
- // Extract reset time
330
- const resetTime = quotaInfo.resetTime || quotaInfo.reset_time || null;
331
- // If remaining is not a valid number but resetTime exists, treat as exhausted (0%)
332
- // This happens when Claude models hit quota limit - API returns resetTime but no fraction
333
- let percentage;
334
- if (typeof remaining === 'number' && isFinite(remaining)) {
335
- percentage = Math.max(0, Math.min(100, Math.round(remaining * 100)));
336
- }
337
- else if (resetTime) {
338
- // Model is exhausted but has reset time - show as 0%
339
- percentage = 0;
340
- }
341
- else {
342
- // No valid data, skip this model
343
- continue;
344
- }
345
- models.push({
346
- name: modelId,
347
- displayName: modelData.displayName,
348
- percentage,
349
- resetTime,
350
- });
351
- }
352
- }
412
+ const response = await performAntigravityRequest(accountId, accessToken, url, FETCHMODELS_HEADERS, JSON.stringify({}));
413
+ if (response.status < 200 || response.status >= 300) {
353
414
  return {
354
- success: true,
355
- models,
415
+ success: false,
416
+ models: [],
356
417
  lastUpdated: Date.now(),
418
+ ...buildAntigravityFailure(response.status, response.bodyText),
357
419
  };
358
420
  }
359
- catch (err) {
360
- clearTimeout(timeoutId);
421
+ const data = response.json;
422
+ if (!data) {
361
423
  return {
362
424
  success: false,
363
425
  models: [],
364
426
  lastUpdated: Date.now(),
365
- error: err instanceof Error ? err.message : 'Unknown error',
427
+ error: 'Invalid quota response from provider',
428
+ errorCode: 'provider_unavailable',
429
+ retryable: true,
366
430
  };
367
431
  }
432
+ const models = [];
433
+ if (data.models && typeof data.models === 'object') {
434
+ for (const [modelId, modelData] of Object.entries(data.models)) {
435
+ const quotaInfo = modelData.quotaInfo || modelData.quota_info;
436
+ if (!quotaInfo)
437
+ continue;
438
+ const remaining = quotaInfo.remainingFraction ?? quotaInfo.remaining_fraction ?? quotaInfo.remaining;
439
+ const resetTime = quotaInfo.resetTime || quotaInfo.reset_time || null;
440
+ let percentage;
441
+ if (typeof remaining === 'number' && isFinite(remaining)) {
442
+ percentage = Math.max(0, Math.min(100, Math.round(remaining * 100)));
443
+ }
444
+ else if (resetTime) {
445
+ percentage = 0;
446
+ }
447
+ else {
448
+ continue;
449
+ }
450
+ models.push({
451
+ name: modelId,
452
+ displayName: modelData.displayName,
453
+ percentage,
454
+ resetTime,
455
+ });
456
+ }
457
+ }
458
+ return {
459
+ success: true,
460
+ models,
461
+ lastUpdated: Date.now(),
462
+ };
368
463
  }
369
464
  /**
370
465
  * Fetch quota for an Antigravity account
@@ -400,56 +495,44 @@ async function fetchAccountQuota(provider, accountId, verbose = false) {
400
495
  models: [],
401
496
  lastUpdated: Date.now(),
402
497
  error,
498
+ errorCode: 'auth_file_missing',
499
+ actionHint: 'Reconnect this account so CCS can read a current auth token.',
403
500
  };
404
501
  }
405
- // Determine which access token to use
406
- // File-based token is often stale (CLIProxyAPIPlus refreshes at runtime but doesn't persist)
407
- // Proactive refresh: refresh 5 minutes before expiry (matches CLIProxyAPIPlus behavior)
408
- let accessToken = authData.accessToken;
409
- const REFRESH_LEAD_TIME_MS = 5 * 60 * 1000; // 5 minutes
410
- let tokenRefreshed = false;
411
- if (authData.refreshToken) {
412
- const shouldRefresh = authData.isExpired || // Already expired
413
- !authData.expiresAt || // No expiry info - refresh to be safe
414
- new Date(authData.expiresAt).getTime() - Date.now() < REFRESH_LEAD_TIME_MS; // Expiring soon
415
- if (shouldRefresh) {
416
- const refreshResult = await refreshAccessToken(authData.refreshToken, verbose);
417
- if (refreshResult.accessToken) {
418
- accessToken = refreshResult.accessToken;
419
- tokenRefreshed = true;
420
- }
421
- // If refresh fails, fall back to existing token (might still work)
422
- }
423
- }
424
- if (verbose && !tokenRefreshed) {
425
- console.error('[i] Token refresh: skipped');
502
+ const accessToken = authData.accessToken;
503
+ if (verbose) {
504
+ const expiryState = authData.isExpired
505
+ ? 'expired'
506
+ : authData.expiresAt
507
+ ? `expires ${authData.expiresAt}`
508
+ : 'expiry unknown';
509
+ console.error(`[i] Auth token state: ${expiryState}`);
426
510
  }
427
511
  // Get project ID and tier - prefer stored project ID, but always call API for tier
428
512
  let projectId = authData.projectId;
429
513
  let apiTier = 'unknown';
430
- // Always call loadCodeAssist to get accurate tier from API
431
- let lastProjectResult = await getProjectId(accessToken);
514
+ // Always call loadCodeAssist to get accurate tier from API.
515
+ // If the file token is stale, the helper retries through CLIProxy management auth.
516
+ const lastProjectResult = await getProjectId(accountId, accessToken);
432
517
  if (!lastProjectResult.projectId && !projectId) {
433
- // If project ID fetch fails, it might be token issue - try refresh if we haven't
434
- if (authData.refreshToken && accessToken === authData.accessToken) {
435
- const refreshResult = await refreshAccessToken(authData.refreshToken, verbose);
436
- if (refreshResult.accessToken) {
437
- accessToken = refreshResult.accessToken;
438
- lastProjectResult = await getProjectId(accessToken);
439
- }
440
- }
441
- if (!lastProjectResult.projectId) {
442
- const error = lastProjectResult.error || 'Failed to retrieve project ID';
443
- if (verbose)
444
- console.error(`[!] Error: ${error}`);
445
- return {
446
- success: false,
447
- models: [],
448
- lastUpdated: Date.now(),
449
- error,
450
- isUnprovisioned: lastProjectResult.isUnprovisioned,
451
- };
452
- }
518
+ const error = lastProjectResult.error || 'Failed to retrieve project ID';
519
+ if (verbose)
520
+ console.error(`[!] Error: ${error}`);
521
+ return {
522
+ success: false,
523
+ models: [],
524
+ lastUpdated: Date.now(),
525
+ error,
526
+ errorCode: lastProjectResult.errorCode,
527
+ errorDetail: lastProjectResult.errorDetail,
528
+ actionHint: lastProjectResult.actionHint,
529
+ retryable: lastProjectResult.retryable,
530
+ httpStatus: lastProjectResult.httpStatus,
531
+ needsReauth: lastProjectResult.needsReauth,
532
+ isUnprovisioned: lastProjectResult.isUnprovisioned,
533
+ isExpired: authData.isExpired,
534
+ expiresAt: authData.expiresAt || undefined,
535
+ };
453
536
  }
454
537
  // Use API project ID if available, else fallback to stored
455
538
  projectId = lastProjectResult.projectId || projectId;
@@ -457,38 +540,23 @@ async function fetchAccountQuota(provider, accountId, verbose = false) {
457
540
  if (verbose)
458
541
  console.error(`[i] Project ID: ${projectId || 'not found'}`);
459
542
  // Fetch models with quota
460
- const result = await fetchAvailableModels(accessToken, projectId);
543
+ const result = await fetchAvailableModels(accountId, accessToken, projectId);
461
544
  if (verbose)
462
545
  console.error(`[i] Models found: ${result.models.length}`);
463
- // If quota fetch fails with auth error and we haven't refreshed yet, try refresh
464
- if (!result.success && result.error?.includes('expired') && authData.refreshToken) {
465
- const refreshResult = await refreshAccessToken(authData.refreshToken, verbose);
466
- if (refreshResult.accessToken) {
467
- const retryResult = await fetchAvailableModels(refreshResult.accessToken, projectId);
468
- // Determine tier from API response only (model inference is unreliable)
469
- if (retryResult.success) {
470
- const finalTier = apiTier !== 'unknown' ? apiTier : 'unknown';
471
- retryResult.tier = finalTier;
472
- retryResult.accountId = accountId;
473
- if (finalTier !== 'unknown') {
474
- (0, account_manager_1.setAccountTier)(provider, accountId, finalTier);
475
- }
476
- if (verbose && retryResult.error) {
477
- console.log(`[!] Error: ${retryResult.error}`);
478
- }
479
- }
480
- return retryResult;
481
- }
482
- }
546
+ result.accountId = accountId;
547
+ result.projectId = projectId || undefined;
483
548
  // Determine tier from API response only
484
549
  if (result.success) {
485
550
  const finalTier = apiTier !== 'unknown' ? apiTier : 'unknown';
486
551
  result.tier = finalTier;
487
- result.accountId = accountId;
488
552
  if (finalTier !== 'unknown') {
489
553
  (0, account_manager_1.setAccountTier)(provider, accountId, finalTier);
490
554
  }
491
555
  }
556
+ else {
557
+ result.isExpired = authData.isExpired;
558
+ result.expiresAt = authData.expiresAt || undefined;
559
+ }
492
560
  if (verbose && result.error) {
493
561
  console.log(`[!] Error: ${result.error}`);
494
562
  }