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,356 @@
1
+ /**
2
+ * Account Manager Component
3
+ * Registers itself to window.Components for Alpine.js to consume
4
+ */
5
+ window.Components = window.Components || {};
6
+
7
+ window.Components.accountManager = () => ({
8
+ searchQuery: '',
9
+ deleteTarget: '',
10
+ refreshing: false,
11
+ toggling: false,
12
+ deleting: false,
13
+ reloading: false,
14
+ selectedAccountEmail: '',
15
+ selectedAccountLimits: {},
16
+
17
+ get filteredAccounts() {
18
+ const accounts = Alpine.store('data').accounts || [];
19
+ if (!this.searchQuery || this.searchQuery.trim() === '') {
20
+ return accounts;
21
+ }
22
+
23
+ const query = this.searchQuery.toLowerCase().trim();
24
+ return accounts.filter(acc => {
25
+ return acc.email.toLowerCase().includes(query) ||
26
+ (acc.projectId && acc.projectId.toLowerCase().includes(query)) ||
27
+ (acc.source && acc.source.toLowerCase().includes(query));
28
+ });
29
+ },
30
+
31
+ formatEmail(email) {
32
+ if (!email || email.length <= 40) return email;
33
+
34
+ const [user, domain] = email.split('@');
35
+ if (!domain) return email;
36
+
37
+ // Preserve domain integrity, truncate username if needed
38
+ if (user.length > 20) {
39
+ return `${user.substring(0, 10)}...${user.slice(-5)}@${domain}`;
40
+ }
41
+ return email;
42
+ },
43
+
44
+ getProviderName(account) {
45
+ const provider = account.provider || 'google';
46
+ const names = {
47
+ google: 'Google',
48
+ anthropic: 'Anthropic',
49
+ openai: 'OpenAI',
50
+ github: 'GitHub',
51
+ copilot: 'Copilot',
52
+ openrouter: 'OpenRouter'
53
+ };
54
+ return names[provider] || provider.toUpperCase();
55
+ },
56
+
57
+ getProviderColor(account) {
58
+ const provider = account.provider || 'google';
59
+ const colors = {
60
+ google: '#4285f4',
61
+ anthropic: '#d97706',
62
+ openai: '#10b981',
63
+ github: '#6366f1',
64
+ copilot: '#f97316',
65
+ openrouter: '#6d28d9'
66
+ };
67
+ return colors[provider] || '#4285f4';
68
+ },
69
+
70
+ async refreshAccount(email) {
71
+ return await window.ErrorHandler.withLoading(async () => {
72
+ const store = Alpine.store('global');
73
+ store.showToast(store.t('refreshingAccount', { email }), 'info');
74
+
75
+ const { response, newPassword } = await window.utils.request(
76
+ `/api/accounts/${encodeURIComponent(email)}/refresh`,
77
+ { method: 'POST' },
78
+ store.webuiPassword
79
+ );
80
+ if (newPassword) store.webuiPassword = newPassword;
81
+
82
+ const data = await response.json();
83
+ if (data.status === 'ok') {
84
+ store.showToast(store.t('refreshedAccount', { email }), 'success');
85
+ Alpine.store('data').fetchData();
86
+ } else {
87
+ throw new Error(data.error || store.t('refreshFailed'));
88
+ }
89
+ }, this, 'refreshing', { errorMessage: 'Failed to refresh account' });
90
+ },
91
+
92
+ async toggleAccount(email, enabled) {
93
+ const store = Alpine.store('global');
94
+ const password = store.webuiPassword;
95
+
96
+ // Optimistic update: immediately update UI
97
+ const dataStore = Alpine.store('data');
98
+ const account = dataStore.accounts.find(a => a.email === email);
99
+ if (account) {
100
+ account.enabled = enabled;
101
+ }
102
+
103
+ try {
104
+ const { response, newPassword } = await window.utils.request(`/api/accounts/${encodeURIComponent(email)}/toggle`, {
105
+ method: 'POST',
106
+ headers: { 'Content-Type': 'application/json' },
107
+ body: JSON.stringify({ enabled })
108
+ }, password);
109
+ if (newPassword) store.webuiPassword = newPassword;
110
+
111
+ const data = await response.json();
112
+ if (data.status === 'ok') {
113
+ const status = enabled ? store.t('enabledStatus') : store.t('disabledStatus');
114
+ store.showToast(store.t('accountToggled', { email, status }), 'success');
115
+ // Refresh to confirm server state
116
+ await dataStore.fetchData();
117
+ } else {
118
+ store.showToast(data.error || store.t('toggleFailed'), 'error');
119
+ // Rollback optimistic update on error
120
+ if (account) {
121
+ account.enabled = !enabled;
122
+ }
123
+ await dataStore.fetchData();
124
+ }
125
+ } catch (e) {
126
+ store.showToast(store.t('toggleFailed') + ': ' + e.message, 'error');
127
+ // Rollback optimistic update on error
128
+ if (account) {
129
+ account.enabled = !enabled;
130
+ }
131
+ await dataStore.fetchData();
132
+ }
133
+ },
134
+
135
+ async fixAccount(email) {
136
+ const store = Alpine.store('global');
137
+ store.showToast(store.t('reauthenticating', { email }), 'info');
138
+ const password = store.webuiPassword;
139
+ try {
140
+ const urlPath = `/api/auth/url?email=${encodeURIComponent(email)}`;
141
+ const { response, newPassword } = await window.utils.request(urlPath, {}, password);
142
+ if (newPassword) store.webuiPassword = newPassword;
143
+
144
+ const data = await response.json();
145
+ if (data.status === 'ok') {
146
+ window.open(data.url, 'google_oauth', 'width=600,height=700,scrollbars=yes');
147
+ } else {
148
+ store.showToast(data.error || store.t('authUrlFailed'), 'error');
149
+ }
150
+ } catch (e) {
151
+ store.showToast(store.t('authUrlFailed') + ': ' + e.message, 'error');
152
+ }
153
+ },
154
+
155
+ confirmDeleteAccount(email) {
156
+ this.deleteTarget = email;
157
+ document.getElementById('delete_account_modal').showModal();
158
+ },
159
+
160
+ async executeDelete() {
161
+ const email = this.deleteTarget;
162
+ return await window.ErrorHandler.withLoading(async () => {
163
+ const store = Alpine.store('global');
164
+
165
+ const { response, newPassword } = await window.utils.request(
166
+ `/api/accounts/${encodeURIComponent(email)}`,
167
+ { method: 'DELETE' },
168
+ store.webuiPassword
169
+ );
170
+ if (newPassword) store.webuiPassword = newPassword;
171
+
172
+ const data = await response.json();
173
+ if (data.status === 'ok') {
174
+ store.showToast(store.t('deletedAccount', { email }), 'success');
175
+ Alpine.store('data').fetchData();
176
+ document.getElementById('delete_account_modal').close();
177
+ this.deleteTarget = '';
178
+ } else {
179
+ throw new Error(data.error || store.t('deleteFailed'));
180
+ }
181
+ }, this, 'deleting', { errorMessage: 'Failed to delete account' });
182
+ },
183
+
184
+ async reloadAccounts() {
185
+ return await window.ErrorHandler.withLoading(async () => {
186
+ const store = Alpine.store('global');
187
+
188
+ const { response, newPassword } = await window.utils.request(
189
+ '/api/accounts/reload',
190
+ { method: 'POST' },
191
+ store.webuiPassword
192
+ );
193
+ if (newPassword) store.webuiPassword = newPassword;
194
+
195
+ const data = await response.json();
196
+ if (data.status === 'ok') {
197
+ store.showToast(store.t('accountsReloaded'), 'success');
198
+ Alpine.store('data').fetchData();
199
+ } else {
200
+ throw new Error(data.error || store.t('reloadFailed'));
201
+ }
202
+ }, this, 'reloading', { errorMessage: 'Failed to reload accounts' });
203
+ },
204
+
205
+ openQuotaModal(account) {
206
+ this.selectedAccountEmail = account.email;
207
+ this.selectedAccountLimits = account.limits || {};
208
+ document.getElementById('quota_modal').showModal();
209
+ },
210
+
211
+ /**
212
+ * Get main model quota for display
213
+ * Prioritizes flagship models (Opus > Sonnet > Flash)
214
+ * @param {Object} account - Account object with limits
215
+ * @returns {Object} { percent: number|null, model: string }
216
+ */
217
+ getMainModelQuota(account) {
218
+ const limits = account.limits || {};
219
+
220
+ const getQuotaVal = (id) => {
221
+ const l = limits[id];
222
+ if (!l) return -1;
223
+ if (l.remainingFraction !== null) return l.remainingFraction;
224
+ if (l.resetTime) return 0; // Rate limited
225
+ return -1; // Unknown
226
+ };
227
+
228
+ const validIds = Object.keys(limits).filter(id => getQuotaVal(id) >= 0);
229
+
230
+ if (validIds.length === 0) return { percent: null, model: '-' };
231
+
232
+ const DEAD_THRESHOLD = 0.01;
233
+
234
+ const MODEL_TIERS = [
235
+ { pattern: /\bopus\b/, aliveScore: 100, deadScore: 60 },
236
+ { pattern: /\bsonnet\b/, aliveScore: 90, deadScore: 55 },
237
+ // Gemini 3 Pro / Ultra
238
+ { pattern: /\bgemini-3\b/, extraCheck: (l) => /\bpro\b/.test(l) || /\bultra\b/.test(l), aliveScore: 80, deadScore: 50 },
239
+ { pattern: /\bpro\b/, aliveScore: 75, deadScore: 45 },
240
+ // Mid/Low Tier
241
+ { pattern: /\bhaiku\b/, aliveScore: 30, deadScore: 15 },
242
+ { pattern: /\bflash\b/, aliveScore: 20, deadScore: 10 }
243
+ ];
244
+
245
+ const getPriority = (id) => {
246
+ const lower = id.toLowerCase();
247
+ const val = getQuotaVal(id);
248
+ const isAlive = val > DEAD_THRESHOLD;
249
+
250
+ for (const tier of MODEL_TIERS) {
251
+ if (tier.pattern.test(lower)) {
252
+ if (tier.extraCheck && !tier.extraCheck(lower)) continue;
253
+ return isAlive ? tier.aliveScore : tier.deadScore;
254
+ }
255
+ }
256
+
257
+ return isAlive ? 5 : 0;
258
+ };
259
+
260
+ // Sort by priority desc
261
+ validIds.sort((a, b) => getPriority(b) - getPriority(a));
262
+
263
+ const bestModel = validIds[0];
264
+ const val = getQuotaVal(bestModel);
265
+
266
+ return {
267
+ percent: Math.round(val * 100),
268
+ model: bestModel
269
+ };
270
+ },
271
+
272
+ /**
273
+ * Export accounts to JSON file
274
+ */
275
+ async exportAccounts() {
276
+ const store = Alpine.store('global');
277
+ try {
278
+ const { response, newPassword } = await window.utils.request(
279
+ '/api/accounts/export',
280
+ {},
281
+ store.webuiPassword
282
+ );
283
+ if (newPassword) store.webuiPassword = newPassword;
284
+
285
+ const data = await response.json();
286
+ // API returns plain array directly
287
+ if (Array.isArray(data)) {
288
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
289
+ const url = URL.createObjectURL(blob);
290
+ const a = document.createElement('a');
291
+ a.href = url;
292
+ a.download = `commons-accounts-${new Date().toISOString().split('T')[0]}.json`;
293
+ document.body.appendChild(a);
294
+ a.click();
295
+ document.body.removeChild(a);
296
+ URL.revokeObjectURL(url);
297
+
298
+ store.showToast(store.t('exportSuccess', { count: data.length }), 'success');
299
+ } else if (data.error) {
300
+ throw new Error(data.error);
301
+ }
302
+ } catch (e) {
303
+ store.showToast(store.t('exportFailed') + ': ' + e.message, 'error');
304
+ }
305
+ },
306
+
307
+ /**
308
+ * Import accounts from JSON file
309
+ * @param {Event} event - file input change event
310
+ */
311
+ async importAccounts(event) {
312
+ const store = Alpine.store('global');
313
+ const file = event.target.files?.[0];
314
+ if (!file) return;
315
+
316
+ try {
317
+ const text = await file.text();
318
+ const importData = JSON.parse(text);
319
+
320
+ // Support both plain array and wrapped format
321
+ const accounts = Array.isArray(importData) ? importData : (importData.accounts || []);
322
+ if (!Array.isArray(accounts) || accounts.length === 0) {
323
+ throw new Error('Invalid file format: expected accounts array');
324
+ }
325
+
326
+ const { response, newPassword } = await window.utils.request(
327
+ '/api/accounts/import',
328
+ {
329
+ method: 'POST',
330
+ headers: { 'Content-Type': 'application/json' },
331
+ body: JSON.stringify(accounts)
332
+ },
333
+ store.webuiPassword
334
+ );
335
+ if (newPassword) store.webuiPassword = newPassword;
336
+
337
+ const data = await response.json();
338
+ if (data.status === 'ok') {
339
+ const { added, updated, failed } = data.results;
340
+ let msg = store.t('importSuccess') + ` ${added.length} added, ${updated.length} updated`;
341
+ if (failed.length > 0) {
342
+ msg += `, ${failed.length} failed`;
343
+ }
344
+ store.showToast(msg, failed.length > 0 ? 'info' : 'success');
345
+ Alpine.store('data').fetchData();
346
+ } else {
347
+ throw new Error(data.error || 'Import failed');
348
+ }
349
+ } catch (e) {
350
+ store.showToast(store.t('importFailed') + ': ' + e.message, 'error');
351
+ } finally {
352
+ // Reset file input
353
+ event.target.value = '';
354
+ }
355
+ }
356
+ });