commons-proxy 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +757 -0
  3. package/bin/cli.js +146 -0
  4. package/package.json +97 -0
  5. package/public/Complaint Details.pdf +0 -0
  6. package/public/Cyber Crime Portal.pdf +0 -0
  7. package/public/app.js +229 -0
  8. package/public/css/src/input.css +523 -0
  9. package/public/css/style.css +1 -0
  10. package/public/favicon.png +0 -0
  11. package/public/index.html +549 -0
  12. package/public/js/components/account-manager.js +356 -0
  13. package/public/js/components/add-account-modal.js +414 -0
  14. package/public/js/components/claude-config.js +420 -0
  15. package/public/js/components/dashboard/charts.js +605 -0
  16. package/public/js/components/dashboard/filters.js +362 -0
  17. package/public/js/components/dashboard/stats.js +110 -0
  18. package/public/js/components/dashboard.js +236 -0
  19. package/public/js/components/logs-viewer.js +100 -0
  20. package/public/js/components/models.js +36 -0
  21. package/public/js/components/server-config.js +349 -0
  22. package/public/js/config/constants.js +102 -0
  23. package/public/js/data-store.js +375 -0
  24. package/public/js/settings-store.js +58 -0
  25. package/public/js/store.js +99 -0
  26. package/public/js/translations/en.js +367 -0
  27. package/public/js/translations/id.js +412 -0
  28. package/public/js/translations/pt.js +308 -0
  29. package/public/js/translations/tr.js +358 -0
  30. package/public/js/translations/zh.js +373 -0
  31. package/public/js/utils/account-actions.js +189 -0
  32. package/public/js/utils/error-handler.js +96 -0
  33. package/public/js/utils/model-config.js +42 -0
  34. package/public/js/utils/ui-logger.js +143 -0
  35. package/public/js/utils/validators.js +77 -0
  36. package/public/js/utils.js +69 -0
  37. package/public/proxy-server-64.png +0 -0
  38. package/public/views/accounts.html +361 -0
  39. package/public/views/dashboard.html +484 -0
  40. package/public/views/logs.html +97 -0
  41. package/public/views/models.html +331 -0
  42. package/public/views/settings.html +1327 -0
  43. package/src/account-manager/credentials.js +378 -0
  44. package/src/account-manager/index.js +462 -0
  45. package/src/account-manager/onboarding.js +112 -0
  46. package/src/account-manager/rate-limits.js +369 -0
  47. package/src/account-manager/storage.js +160 -0
  48. package/src/account-manager/strategies/base-strategy.js +109 -0
  49. package/src/account-manager/strategies/hybrid-strategy.js +339 -0
  50. package/src/account-manager/strategies/index.js +79 -0
  51. package/src/account-manager/strategies/round-robin-strategy.js +76 -0
  52. package/src/account-manager/strategies/sticky-strategy.js +138 -0
  53. package/src/account-manager/strategies/trackers/health-tracker.js +162 -0
  54. package/src/account-manager/strategies/trackers/index.js +9 -0
  55. package/src/account-manager/strategies/trackers/quota-tracker.js +120 -0
  56. package/src/account-manager/strategies/trackers/token-bucket-tracker.js +155 -0
  57. package/src/auth/database.js +169 -0
  58. package/src/auth/oauth.js +548 -0
  59. package/src/auth/token-extractor.js +117 -0
  60. package/src/cli/accounts.js +648 -0
  61. package/src/cloudcode/index.js +29 -0
  62. package/src/cloudcode/message-handler.js +510 -0
  63. package/src/cloudcode/model-api.js +248 -0
  64. package/src/cloudcode/rate-limit-parser.js +235 -0
  65. package/src/cloudcode/request-builder.js +93 -0
  66. package/src/cloudcode/session-manager.js +47 -0
  67. package/src/cloudcode/sse-parser.js +121 -0
  68. package/src/cloudcode/sse-streamer.js +293 -0
  69. package/src/cloudcode/streaming-handler.js +615 -0
  70. package/src/config.js +125 -0
  71. package/src/constants.js +407 -0
  72. package/src/errors.js +242 -0
  73. package/src/fallback-config.js +29 -0
  74. package/src/format/content-converter.js +193 -0
  75. package/src/format/index.js +20 -0
  76. package/src/format/request-converter.js +255 -0
  77. package/src/format/response-converter.js +120 -0
  78. package/src/format/schema-sanitizer.js +673 -0
  79. package/src/format/signature-cache.js +88 -0
  80. package/src/format/thinking-utils.js +648 -0
  81. package/src/index.js +148 -0
  82. package/src/modules/usage-stats.js +205 -0
  83. package/src/providers/anthropic-provider.js +258 -0
  84. package/src/providers/base-provider.js +157 -0
  85. package/src/providers/cloudcode.js +94 -0
  86. package/src/providers/copilot.js +399 -0
  87. package/src/providers/github-provider.js +287 -0
  88. package/src/providers/google-provider.js +192 -0
  89. package/src/providers/index.js +211 -0
  90. package/src/providers/openai-compatible.js +265 -0
  91. package/src/providers/openai-provider.js +271 -0
  92. package/src/providers/openrouter-provider.js +325 -0
  93. package/src/providers/setup.js +83 -0
  94. package/src/server.js +870 -0
  95. package/src/utils/claude-config.js +245 -0
  96. package/src/utils/helpers.js +51 -0
  97. package/src/utils/logger.js +142 -0
  98. package/src/utils/native-module-helper.js +162 -0
  99. package/src/webui/index.js +1134 -0
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Credentials Management
3
+ *
4
+ * Handles OAuth token handling and project discovery.
5
+ */
6
+
7
+ import {
8
+ CLOUDCODE_DB_PATH,
9
+ TOKEN_REFRESH_INTERVAL_MS,
10
+ LOAD_CODE_ASSIST_ENDPOINTS,
11
+ LOAD_CODE_ASSIST_HEADERS,
12
+ DEFAULT_PROJECT_ID
13
+ } from '../constants.js';
14
+ import { refreshAccessToken, parseRefreshParts, formatRefreshParts } from '../auth/oauth.js';
15
+ import { getAuthStatus } from '../auth/database.js';
16
+ import { logger } from '../utils/logger.js';
17
+ import { isNetworkError } from '../utils/helpers.js';
18
+ import { onboardUser, getDefaultTierId } from './onboarding.js';
19
+ import { parseTierId } from '../cloudcode/model-api.js';
20
+ import { getProviderForAccount } from '../providers/index.js';
21
+
22
+ // Track accounts currently fetching subscription to avoid duplicate calls
23
+ const subscriptionFetchInProgress = new Set();
24
+
25
+ /**
26
+ * Fetch subscription tier and save it (blocking)
27
+ * Used when we have a cached project but missing subscription data
28
+ *
29
+ * @param {string} token - OAuth access token
30
+ * @param {Object} account - Account object
31
+ * @param {Function} [onSave] - Callback to save account changes
32
+ */
33
+ async function fetchAndSaveSubscription(token, account, onSave) {
34
+ // Avoid duplicate fetches for the same account
35
+ if (subscriptionFetchInProgress.has(account.email)) {
36
+ return;
37
+ }
38
+ subscriptionFetchInProgress.add(account.email);
39
+
40
+ try {
41
+ // Call discoverProject just to get subscription info
42
+ const { subscription } = await discoverProject(token, account.projectId);
43
+ if (subscription && subscription.tier !== 'unknown') {
44
+ account.subscription = subscription;
45
+ if (onSave) {
46
+ await onSave();
47
+ }
48
+ logger.info(`[AccountManager] Updated subscription tier for ${account.email}: ${subscription.tier}`);
49
+ }
50
+ } catch (e) {
51
+ logger.debug(`[AccountManager] Subscription fetch failed for ${account.email}: ${e.message}`);
52
+ } finally {
53
+ subscriptionFetchInProgress.delete(account.email);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Get OAuth token for an account using provider abstraction
59
+ *
60
+ * @param {Object} account - Account object with email and credentials
61
+ * @param {Map} tokenCache - Token cache map
62
+ * @param {Function} onInvalid - Callback when account is invalid (email, reason)
63
+ * @param {Function} onSave - Callback to save changes
64
+ * @returns {Promise<string>} OAuth access token
65
+ * @throws {Error} If token refresh fails
66
+ */
67
+ export async function getTokenForAccount(account, tokenCache, onInvalid, onSave) {
68
+ // Check cache first
69
+ const cached = tokenCache.get(account.email);
70
+ if (cached && (Date.now() - cached.extractedAt) < TOKEN_REFRESH_INTERVAL_MS) {
71
+ return cached.token;
72
+ }
73
+
74
+ let token;
75
+
76
+ try {
77
+ // Use provider system to get access token
78
+ const provider = getProviderForAccount(account);
79
+ token = await provider.getAccessToken(account);
80
+
81
+ // Clear invalid flag on success
82
+ if (account.isInvalid) {
83
+ account.isInvalid = false;
84
+ account.invalidReason = null;
85
+ if (onSave) await onSave();
86
+ }
87
+
88
+ logger.success(`[AccountManager] Got access token for ${account.email} (${provider.name})`);
89
+ } catch (error) {
90
+ // Check if it's a transient network error
91
+ if (isNetworkError(error)) {
92
+ logger.warn(`[AccountManager] Failed to get token for ${account.email} due to network error: ${error.message}`);
93
+ // Do NOT mark as invalid, just throw so caller knows it failed
94
+ throw new Error(`AUTH_NETWORK_ERROR: ${error.message}`);
95
+ }
96
+
97
+ logger.error(`[AccountManager] Failed to get token for ${account.email}:`, error.message);
98
+
99
+ // Check if provider recommends invalidating credentials
100
+ try {
101
+ const provider = getProviderForAccount(account);
102
+ if (provider.shouldInvalidateCredentials(error)) {
103
+ if (onInvalid) onInvalid(account.email, error.message);
104
+ }
105
+ } catch (providerError) {
106
+ // Provider detection failed, use legacy behavior
107
+ logger.debug('[AccountManager] Provider detection failed, using legacy error handling');
108
+ }
109
+
110
+ throw new Error(`AUTH_INVALID: ${account.email}: ${error.message}`);
111
+ }
112
+
113
+ // Cache the token
114
+ tokenCache.set(account.email, {
115
+ token,
116
+ extractedAt: Date.now()
117
+ });
118
+
119
+ return token;
120
+ }
121
+
122
+ /**
123
+ * Get project ID for an account
124
+ * Aligned with opencode-cloudcode-auth: parses refresh token for stored project IDs
125
+ *
126
+ * @param {Object} account - Account object
127
+ * @param {string} token - OAuth access token
128
+ * @param {Map} projectCache - Project cache map
129
+ * @param {Function} [onSave] - Callback to save account changes
130
+ * @returns {Promise<string>} Project ID
131
+ */
132
+ export async function getProjectForAccount(account, token, projectCache, onSave = null) {
133
+ // Check cache first
134
+ const cached = projectCache.get(account.email);
135
+ if (cached) {
136
+ return cached;
137
+ }
138
+
139
+ // Parse refresh token to get stored project IDs (aligned with opencode-cloudcode-auth)
140
+ const parts = account.refreshToken ? parseRefreshParts(account.refreshToken) : { refreshToken: null, projectId: undefined, managedProjectId: undefined };
141
+
142
+ // If we have a managedProjectId in the refresh token, use it
143
+ if (parts.managedProjectId) {
144
+ projectCache.set(account.email, parts.managedProjectId);
145
+ // If subscription is missing/unknown, fetch it now (blocking)
146
+ if (!account.subscription || account.subscription.tier === 'unknown') {
147
+ await fetchAndSaveSubscription(token, account, onSave);
148
+ }
149
+ return parts.managedProjectId;
150
+ }
151
+
152
+ // Legacy: check account.projectId for backward compatibility
153
+ if (account.projectId) {
154
+ projectCache.set(account.email, account.projectId);
155
+ // If subscription is missing/unknown, fetch it now (blocking)
156
+ if (!account.subscription || account.subscription.tier === 'unknown') {
157
+ await fetchAndSaveSubscription(token, account, onSave);
158
+ }
159
+ return account.projectId;
160
+ }
161
+
162
+ // Discover managed project, passing projectId for metadata.duetProject
163
+ // Reference: opencode-cloudcode-auth - discoverProject handles fallback internally
164
+ const { project, subscription } = await discoverProject(token, parts.projectId);
165
+
166
+ // Store managedProjectId back in refresh token (if we got a real project)
167
+ if (project && project !== DEFAULT_PROJECT_ID) {
168
+ let needsSave = false;
169
+
170
+ if (account.refreshToken) {
171
+ // OAuth accounts: encode in refresh token
172
+ account.refreshToken = formatRefreshParts({
173
+ refreshToken: parts.refreshToken,
174
+ projectId: parts.projectId,
175
+ managedProjectId: project,
176
+ });
177
+ needsSave = true;
178
+ } else if (account.source === 'database' || account.source === 'manual') {
179
+ // Database/manual accounts: store in projectId field
180
+ account.projectId = project;
181
+ needsSave = true;
182
+ }
183
+
184
+ // Save subscription tier if discovered
185
+ if (subscription) {
186
+ account.subscription = subscription;
187
+ needsSave = true;
188
+ }
189
+
190
+ // Trigger save to persist the updated project and subscription
191
+ if (needsSave && onSave) {
192
+ try {
193
+ await onSave();
194
+ } catch (e) {
195
+ logger.warn(`[AccountManager] Failed to save updated project: ${e.message}`);
196
+ }
197
+ }
198
+ } else if (subscription) {
199
+ // Even if no project discovered, save subscription if we got it
200
+ account.subscription = subscription;
201
+ if (onSave) {
202
+ try {
203
+ await onSave();
204
+ } catch (e) {
205
+ logger.warn(`[AccountManager] Failed to save subscription: ${e.message}`);
206
+ }
207
+ }
208
+ }
209
+
210
+ projectCache.set(account.email, project);
211
+ return project;
212
+ }
213
+
214
+ /**
215
+ * Discover project ID via Cloud Code API
216
+ *
217
+ * @param {string} token - OAuth access token
218
+ * @param {string} [projectId] - Optional project ID from refresh token (for metadata.duetProject)
219
+ * @returns {Promise<{project: string, subscription: {tier: string, projectId: string|null, detectedAt: string}|null}>} Project and subscription info
220
+ */
221
+ export async function discoverProject(token, projectId = undefined) {
222
+ let lastError = null;
223
+ let gotSuccessfulResponse = false;
224
+ let loadCodeAssistData = null;
225
+
226
+ const metadata = {
227
+ ideType: 'IDE_UNSPECIFIED',
228
+ platform: 'PLATFORM_UNSPECIFIED',
229
+ pluginType: 'GEMINI'
230
+ };
231
+ if (projectId) {
232
+ metadata.duetProject = projectId;
233
+ }
234
+
235
+ for (const endpoint of LOAD_CODE_ASSIST_ENDPOINTS) {
236
+ try {
237
+ const response = await fetch(`${endpoint}/v1internal:loadCodeAssist`, {
238
+ method: 'POST',
239
+ headers: {
240
+ 'Authorization': `Bearer ${token}`,
241
+ 'Content-Type': 'application/json',
242
+ ...LOAD_CODE_ASSIST_HEADERS
243
+ },
244
+ body: JSON.stringify({ metadata })
245
+ });
246
+
247
+ if (!response.ok) {
248
+ const errorText = await response.text();
249
+ lastError = `${response.status} - ${errorText}`;
250
+ logger.debug(`[AccountManager] loadCodeAssist failed at ${endpoint}: ${lastError}`);
251
+ continue;
252
+ }
253
+
254
+ const data = await response.json();
255
+ gotSuccessfulResponse = true;
256
+ loadCodeAssistData = data;
257
+
258
+ logger.debug(`[AccountManager] loadCodeAssist response from ${endpoint}:`, JSON.stringify(data));
259
+
260
+ // Extract subscription tier from response
261
+ const subscription = extractSubscriptionFromResponse(data);
262
+
263
+ if (typeof data.cloudaicompanionProject === 'string') {
264
+ logger.success(`[AccountManager] Discovered project: ${data.cloudaicompanionProject}`);
265
+ return { project: data.cloudaicompanionProject, subscription };
266
+ }
267
+ if (data.cloudaicompanionProject?.id) {
268
+ logger.success(`[AccountManager] Discovered project: ${data.cloudaicompanionProject.id}`);
269
+ return { project: data.cloudaicompanionProject.id, subscription };
270
+ }
271
+
272
+ // No project found - log tier data and try to onboard the user
273
+ logger.info(`[AccountManager] No project in loadCodeAssist response, attempting onboardUser...`);
274
+ logger.debug(`[AccountManager] Tier data for onboarding: paidTier=${JSON.stringify(data.paidTier)}, currentTier=${JSON.stringify(data.currentTier)}, allowedTiers=${JSON.stringify(data.allowedTiers?.map(t => ({ id: t?.id, isDefault: t?.isDefault })))}`);
275
+ break;
276
+ } catch (error) {
277
+ lastError = error.message;
278
+ logger.debug(`[AccountManager] loadCodeAssist error at ${endpoint}:`, error.message);
279
+ }
280
+ }
281
+
282
+ // If we got a successful response but no project, try onboarding
283
+ if (gotSuccessfulResponse && loadCodeAssistData) {
284
+ // Only use allowedTiers for onboarding (matching opencode-cloudcode-auth and oauth.js)
285
+ // Note: paidTier (g1-pro-tier, g1-ultra-tier) is NOT valid for onboardUser API
286
+ // The paidTier is used for subscription detection only, not for onboarding
287
+ const tierId = getDefaultTierId(loadCodeAssistData.allowedTiers) || 'free-tier';
288
+ logger.info(`[AccountManager] Onboarding user with tier: ${tierId}`);
289
+
290
+ // Pass projectId for metadata.duetProject (without fallback, matching reference)
291
+ // Reference: opencode-cloudcode-auth passes parts.projectId (not fallback) to onboardManagedProject
292
+ const onboardedProject = await onboardUser(
293
+ token,
294
+ tierId,
295
+ projectId // Original projectId without fallback
296
+ );
297
+ if (onboardedProject) {
298
+ logger.success(`[AccountManager] Successfully onboarded, project: ${onboardedProject}`);
299
+ const subscription = extractSubscriptionFromResponse(loadCodeAssistData);
300
+ return { project: onboardedProject, subscription };
301
+ }
302
+
303
+ logger.warn(`[AccountManager] Onboarding failed - account may not work correctly`);
304
+ }
305
+
306
+ // Only warn if all endpoints failed with errors (not just missing project)
307
+ if (!gotSuccessfulResponse) {
308
+ logger.warn(`[AccountManager] loadCodeAssist failed for all endpoints: ${lastError}`);
309
+ }
310
+
311
+ // Fallback: use projectId if available, otherwise use default
312
+ // Reference: opencode-cloudcode-auth/src/plugin/project.ts
313
+ if (projectId) {
314
+ return { project: projectId, subscription: null };
315
+ }
316
+ return { project: DEFAULT_PROJECT_ID, subscription: null };
317
+ }
318
+
319
+ /**
320
+ * Extract subscription tier from loadCodeAssist response
321
+ *
322
+ * @param {Object} data - loadCodeAssist response data
323
+ * @returns {{tier: string, projectId: string|null, detectedAt: string}|null} Subscription info
324
+ */
325
+ function extractSubscriptionFromResponse(data) {
326
+ if (!data) return null;
327
+
328
+ // Priority: paidTier > currentTier (consistent with model-api.js)
329
+ let tier = 'free';
330
+ let cloudProject = null;
331
+
332
+ if (data.paidTier?.id) {
333
+ tier = parseTierId(data.paidTier.id);
334
+ } else if (data.currentTier?.id) {
335
+ tier = parseTierId(data.currentTier.id);
336
+ }
337
+
338
+ // Get project ID
339
+ if (typeof data.cloudaicompanionProject === 'string') {
340
+ cloudProject = data.cloudaicompanionProject;
341
+ } else if (data.cloudaicompanionProject?.id) {
342
+ cloudProject = data.cloudaicompanionProject.id;
343
+ }
344
+
345
+ return {
346
+ tier,
347
+ projectId: cloudProject,
348
+ detectedAt: new Date().toISOString()
349
+ };
350
+ }
351
+
352
+ /**
353
+ * Clear project cache for an account
354
+ *
355
+ * @param {Map} projectCache - Project cache map
356
+ * @param {string|null} email - Email to clear cache for, or null to clear all
357
+ */
358
+ export function clearProjectCache(projectCache, email = null) {
359
+ if (email) {
360
+ projectCache.delete(email);
361
+ } else {
362
+ projectCache.clear();
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Clear token cache for an account
368
+ *
369
+ * @param {Map} tokenCache - Token cache map
370
+ * @param {string|null} email - Email to clear cache for, or null to clear all
371
+ */
372
+ export function clearTokenCache(tokenCache, email = null) {
373
+ if (email) {
374
+ tokenCache.delete(email);
375
+ } else {
376
+ tokenCache.clear();
377
+ }
378
+ }