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,325 @@
1
+ /**
2
+ * OpenRouter API Provider
3
+ *
4
+ * Implements authentication via OpenRouter API keys.
5
+ * OpenRouter provides a unified API for hundreds of AI models
6
+ * through a single OpenAI-compatible endpoint.
7
+ *
8
+ * API Base: https://openrouter.ai/api/v1
9
+ * Auth: Bearer token (API key)
10
+ * Models endpoint: GET /api/v1/models
11
+ * Chat endpoint: POST /api/v1/chat/completions
12
+ */
13
+
14
+ import BaseProvider from './base-provider.js';
15
+
16
+ export class OpenRouterProvider extends BaseProvider {
17
+ constructor(config = {}) {
18
+ super('openrouter', 'OpenRouter', {
19
+ apiEndpoint: config.apiEndpoint || 'https://openrouter.ai/api/v1',
20
+ ...config
21
+ });
22
+ }
23
+
24
+ /**
25
+ * Validate OpenRouter API key by fetching the auth/key endpoint
26
+ *
27
+ * @param {Object} account - Account with apiKey
28
+ * @returns {Promise<{valid: boolean, error?: string, email?: string}>}
29
+ */
30
+ async validateCredentials(account) {
31
+ if (!account.apiKey) {
32
+ return { valid: false, error: 'Missing API key' };
33
+ }
34
+
35
+ try {
36
+ // OpenRouter provides an auth/key endpoint to validate keys
37
+ const response = await fetch('https://openrouter.ai/api/v1/auth/key', {
38
+ method: 'GET',
39
+ headers: {
40
+ 'Authorization': `Bearer ${account.apiKey}`,
41
+ 'HTTP-Referer': 'https://github.com/AryanVBW/CommonsProxy',
42
+ 'X-Title': 'CommonsProxy'
43
+ }
44
+ });
45
+
46
+ if (!response.ok) {
47
+ const error = await response.text();
48
+ return { valid: false, error: `API key validation failed (${response.status}): ${error}` };
49
+ }
50
+
51
+ const data = await response.json();
52
+
53
+ // Use the label from the key data, or generate an identifier
54
+ const email = account.email || data.data?.label || `openrouter-${account.apiKey.slice(-8)}`;
55
+
56
+ return { valid: true, email };
57
+ } catch (error) {
58
+ this.error('Credential validation failed', error);
59
+ return { valid: false, error: error.message };
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Get API key (for OpenRouter, API key IS the access token)
65
+ *
66
+ * @param {Object} account - Account with apiKey
67
+ * @returns {Promise<string>} API key
68
+ */
69
+ async getAccessToken(account) {
70
+ if (!account.apiKey) {
71
+ throw new Error('Account missing API key');
72
+ }
73
+ return account.apiKey;
74
+ }
75
+
76
+ /**
77
+ * Fetch usage/quota information from OpenRouter
78
+ * OpenRouter provides credit-based usage info via /api/v1/auth/key
79
+ *
80
+ * @param {Object} account - Account object
81
+ * @param {string} token - API key
82
+ * @returns {Promise<Object>} Quota data
83
+ */
84
+ async getQuotas(account, token) {
85
+ try {
86
+ // Fetch key info for credit/usage data
87
+ const keyResponse = await fetch('https://openrouter.ai/api/v1/auth/key', {
88
+ method: 'GET',
89
+ headers: {
90
+ 'Authorization': `Bearer ${token}`,
91
+ 'HTTP-Referer': 'https://github.com/AryanVBW/CommonsProxy',
92
+ 'X-Title': 'CommonsProxy'
93
+ }
94
+ });
95
+
96
+ let creditFraction = 1.0;
97
+ if (keyResponse.ok) {
98
+ const keyData = await keyResponse.json();
99
+ // keyData.data has: { label, usage, limit, is_free_tier, rate_limit }
100
+ const usage = keyData.data?.usage || 0;
101
+ const limit = keyData.data?.limit || null;
102
+ if (limit && limit > 0) {
103
+ creditFraction = Math.max(0, (limit - usage) / limit);
104
+ }
105
+ }
106
+
107
+ // Fetch available models
108
+ const modelsResponse = await fetch('https://openrouter.ai/api/v1/models', {
109
+ method: 'GET',
110
+ headers: {
111
+ 'Authorization': `Bearer ${token}`,
112
+ 'HTTP-Referer': 'https://github.com/AryanVBW/CommonsProxy',
113
+ 'X-Title': 'CommonsProxy'
114
+ }
115
+ });
116
+
117
+ const models = {};
118
+
119
+ if (modelsResponse.ok) {
120
+ const modelsData = await modelsResponse.json();
121
+ if (modelsData.data && Array.isArray(modelsData.data)) {
122
+ // Include popular Claude, GPT, and other models
123
+ const relevantModels = modelsData.data.filter(model => {
124
+ const id = model.id.toLowerCase();
125
+ return id.includes('claude') ||
126
+ id.includes('gpt') ||
127
+ id.includes('gemini') ||
128
+ id.includes('llama') ||
129
+ id.includes('mistral') ||
130
+ id.includes('deepseek') ||
131
+ id.includes('qwen');
132
+ });
133
+
134
+ // Limit to top 50 most relevant models to avoid overwhelming the UI
135
+ relevantModels.slice(0, 50).forEach(model => {
136
+ models[model.id] = {
137
+ remainingFraction: creditFraction,
138
+ resetTime: null
139
+ };
140
+ });
141
+ }
142
+ }
143
+
144
+ // If no models found, add common defaults
145
+ if (Object.keys(models).length === 0) {
146
+ const defaultModels = [
147
+ 'anthropic/claude-sonnet-4',
148
+ 'anthropic/claude-3.5-sonnet',
149
+ 'openai/gpt-4o',
150
+ 'openai/gpt-4o-mini',
151
+ 'google/gemini-2.5-pro-preview',
152
+ 'meta-llama/llama-3.1-405b-instruct',
153
+ 'deepseek/deepseek-r1'
154
+ ];
155
+ defaultModels.forEach(modelId => {
156
+ models[modelId] = {
157
+ remainingFraction: creditFraction,
158
+ resetTime: null
159
+ };
160
+ });
161
+ }
162
+
163
+ return { models };
164
+ } catch (error) {
165
+ this.error('Failed to fetch quotas', error);
166
+ return {
167
+ models: {
168
+ 'anthropic/claude-sonnet-4': { remainingFraction: 1.0, resetTime: null },
169
+ 'openai/gpt-4o': { remainingFraction: 1.0, resetTime: null }
170
+ }
171
+ };
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Get subscription tier from OpenRouter key info
177
+ *
178
+ * @param {Object} account - Account object
179
+ * @param {string} token - API key
180
+ * @returns {Promise<{tier: string, projectId: null}>}
181
+ */
182
+ async getSubscriptionTier(account, token) {
183
+ try {
184
+ const response = await fetch('https://openrouter.ai/api/v1/auth/key', {
185
+ method: 'GET',
186
+ headers: {
187
+ 'Authorization': `Bearer ${token}`,
188
+ 'HTTP-Referer': 'https://github.com/AryanVBW/CommonsProxy',
189
+ 'X-Title': 'CommonsProxy'
190
+ }
191
+ });
192
+
193
+ if (response.ok) {
194
+ const data = await response.json();
195
+ const isFreeTier = data.data?.is_free_tier || false;
196
+ return {
197
+ tier: isFreeTier ? 'free' : 'paid',
198
+ projectId: null
199
+ };
200
+ }
201
+ } catch (error) {
202
+ this.debug('Failed to fetch subscription tier', error.message);
203
+ }
204
+
205
+ return { tier: 'unknown', projectId: null };
206
+ }
207
+
208
+ /**
209
+ * Get available models from OpenRouter
210
+ *
211
+ * @param {Object} account - Account object
212
+ * @param {string} token - API key
213
+ * @returns {Promise<Array>} List of available models
214
+ */
215
+ async getAvailableModels(account, token) {
216
+ try {
217
+ const response = await fetch('https://openrouter.ai/api/v1/models', {
218
+ method: 'GET',
219
+ headers: {
220
+ 'Authorization': `Bearer ${token}`,
221
+ 'HTTP-Referer': 'https://github.com/AryanVBW/CommonsProxy',
222
+ 'X-Title': 'CommonsProxy'
223
+ }
224
+ });
225
+
226
+ if (!response.ok) {
227
+ throw new Error(`Failed to fetch models: ${response.status}`);
228
+ }
229
+
230
+ const data = await response.json();
231
+ if (data.data && Array.isArray(data.data)) {
232
+ return data.data.map(model => {
233
+ const id = model.id.toLowerCase();
234
+ let family = 'other';
235
+ if (id.includes('claude')) family = 'claude';
236
+ else if (id.includes('gpt')) family = 'gpt';
237
+ else if (id.includes('gemini')) family = 'gemini';
238
+ else if (id.startsWith('o1') || id.startsWith('o3')) family = 'o1';
239
+ else if (id.includes('llama')) family = 'llama';
240
+ else if (id.includes('mistral')) family = 'mistral';
241
+ else if (id.includes('deepseek')) family = 'deepseek';
242
+
243
+ return {
244
+ id: model.id,
245
+ name: model.name || model.id,
246
+ family
247
+ };
248
+ });
249
+ }
250
+
251
+ return [];
252
+ } catch (error) {
253
+ this.error('Failed to fetch available models', error);
254
+ return [];
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Parse OpenRouter rate limit headers
260
+ * OpenRouter uses standard rate limit headers
261
+ *
262
+ * @param {Response} response - Fetch response
263
+ * @param {Object} errorData - Error data from response body
264
+ * @returns {Object|null} Rate limit info
265
+ */
266
+ parseRateLimitInfo(response, errorData = null) {
267
+ // OpenRouter uses these headers:
268
+ // x-ratelimit-limit-requests
269
+ // x-ratelimit-remaining-requests
270
+ // x-ratelimit-reset-requests
271
+ const resetRequests = response.headers.get('x-ratelimit-reset-requests');
272
+ const retryAfter = response.headers.get('retry-after');
273
+
274
+ if (retryAfter) {
275
+ const seconds = parseInt(retryAfter, 10);
276
+ if (!isNaN(seconds)) {
277
+ return {
278
+ resetTime: new Date(Date.now() + seconds * 1000),
279
+ retryAfter: seconds
280
+ };
281
+ }
282
+ }
283
+
284
+ if (resetRequests) {
285
+ const resetDate = new Date(resetRequests);
286
+ if (!isNaN(resetDate.getTime())) {
287
+ return {
288
+ resetTime: resetDate,
289
+ retryAfter: Math.max(0, Math.floor((resetDate - Date.now()) / 1000))
290
+ };
291
+ }
292
+ }
293
+
294
+ // Check error response for rate limit info
295
+ if (errorData?.error?.code === 429 || errorData?.error?.message?.includes('rate limit')) {
296
+ return {
297
+ resetTime: new Date(Date.now() + 60000),
298
+ retryAfter: 60
299
+ };
300
+ }
301
+
302
+ return null;
303
+ }
304
+
305
+ /**
306
+ * Check if error indicates invalid API key
307
+ *
308
+ * @param {Error} error - Error object
309
+ * @returns {boolean}
310
+ */
311
+ shouldInvalidateCredentials(error) {
312
+ if (error.message && (
313
+ error.message.includes('invalid_api_key') ||
314
+ error.message.includes('Invalid API key') ||
315
+ error.message.includes('No auth credentials found') ||
316
+ error.message.includes('authentication')
317
+ )) {
318
+ return true;
319
+ }
320
+
321
+ return super.shouldInvalidateCredentials(error);
322
+ }
323
+ }
324
+
325
+ export default OpenRouterProvider;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Provider Setup
3
+ *
4
+ * Initializes and registers all available providers.
5
+ * Called during server startup.
6
+ */
7
+
8
+ import { registerProvider, listProviders, getAllAuthProviders } from './index.js';
9
+ import { CloudCodeProvider } from './cloudcode.js';
10
+ import { createOpenAICompatibleProvider } from './openai-compatible.js';
11
+ import { config } from '../config.js';
12
+ import { logger } from '../utils/logger.js';
13
+
14
+ /**
15
+ * Initialize all providers
16
+ * Registers built-in messaging providers and any custom providers from config.
17
+ * Note: Authentication providers (Google, Anthropic, OpenAI, GitHub, Copilot)
18
+ * are registered automatically in providers/index.js
19
+ */
20
+ export function initializeProviders() {
21
+ logger.info('[Providers] Initializing provider system...');
22
+
23
+ // Register built-in messaging providers (for request routing)
24
+ registerProvider(CloudCodeProvider);
25
+
26
+ // Register custom OpenAI-compatible providers from config
27
+ const customProviders = config?.providers?.custom || [];
28
+ for (const providerConfig of customProviders) {
29
+ if (providerConfig.enabled !== false) {
30
+ try {
31
+ const provider = createOpenAICompatibleProvider(providerConfig);
32
+ registerProvider(provider);
33
+ } catch (error) {
34
+ logger.error(`[Providers] Failed to register custom provider ${providerConfig.id}: ${error.message}`);
35
+ }
36
+ }
37
+ }
38
+
39
+ // Log registered providers
40
+ const messagingProviders = listProviders();
41
+ const authProviders = getAllAuthProviders();
42
+ logger.success(`[Providers] Initialized ${messagingProviders.length} messaging provider(s): ${messagingProviders.map(p => p.id).join(', ')}`);
43
+ logger.success(`[Providers] ${authProviders.length} auth provider(s) available: ${authProviders.map(p => p.id).join(', ')}`);
44
+
45
+ return messagingProviders;
46
+ }
47
+
48
+ /**
49
+ * Get provider configuration schema
50
+ * Used for WebUI settings
51
+ */
52
+ export function getProviderConfigSchema() {
53
+ return {
54
+ cloudcode: {
55
+ name: 'Google Cloud Code',
56
+ description: 'Default provider using Google Cloud Code API',
57
+ configurable: false // Always enabled
58
+ },
59
+ copilot: {
60
+ name: 'GitHub Copilot',
61
+ description: 'Use GitHub Copilot API for model access',
62
+ fields: [
63
+ { key: 'enabled', type: 'boolean', label: 'Enable GitHub Copilot', default: false }
64
+ ]
65
+ },
66
+ custom: {
67
+ name: 'Custom OpenAI-Compatible',
68
+ description: 'Add custom OpenAI-compatible API endpoints',
69
+ fields: [
70
+ { key: 'id', type: 'string', label: 'Provider ID', required: true },
71
+ { key: 'name', type: 'string', label: 'Display Name', required: true },
72
+ { key: 'baseUrl', type: 'string', label: 'API Base URL', required: true },
73
+ { key: 'apiKey', type: 'password', label: 'API Key', required: false },
74
+ { key: 'enabled', type: 'boolean', label: 'Enabled', default: true }
75
+ ]
76
+ }
77
+ };
78
+ }
79
+
80
+ export default {
81
+ initializeProviders,
82
+ getProviderConfigSchema
83
+ };