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,287 @@
1
+ /**
2
+ * GitHub Models Provider
3
+ *
4
+ * Implements authentication via GitHub Personal Access Token (PAT).
5
+ * Supports models from GitHub Models marketplace.
6
+ */
7
+
8
+ import BaseProvider from './base-provider.js';
9
+
10
+ export class GitHubProvider extends BaseProvider {
11
+ constructor(config = {}) {
12
+ super('github', 'GitHub Models', {
13
+ apiEndpoint: config.apiEndpoint || 'https://models.inference.ai.azure.com',
14
+ modelsEndpoint: config.modelsEndpoint || 'https://api.github.com/models',
15
+ ...config
16
+ });
17
+ }
18
+
19
+ /**
20
+ * Validate GitHub PAT
21
+ *
22
+ * @param {Object} account - Account with apiKey (PAT)
23
+ * @returns {Promise<{valid: boolean, error?: string, email?: string}>}
24
+ */
25
+ async validateCredentials(account) {
26
+ if (!account.apiKey) {
27
+ return { valid: false, error: 'Missing Personal Access Token' };
28
+ }
29
+
30
+ try {
31
+ // Verify PAT by fetching user info
32
+ const response = await fetch('https://api.github.com/user', {
33
+ method: 'GET',
34
+ headers: {
35
+ 'Authorization': `Bearer ${account.apiKey}`,
36
+ 'Accept': 'application/vnd.github+json',
37
+ 'X-GitHub-Api-Version': '2022-11-28'
38
+ }
39
+ });
40
+
41
+ if (!response.ok) {
42
+ const error = await response.text();
43
+ return { valid: false, error: `PAT validation failed: ${error}` };
44
+ }
45
+
46
+ const userData = await response.json();
47
+ const email = userData.email || `${userData.login}@github`;
48
+
49
+ return { valid: true, email };
50
+ } catch (error) {
51
+ this.error('Credential validation failed', error);
52
+ return { valid: false, error: error.message };
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Get PAT (for GitHub, PAT IS the access token)
58
+ *
59
+ * @param {Object} account - Account with apiKey (PAT)
60
+ * @returns {Promise<string>} PAT
61
+ */
62
+ async getAccessToken(account) {
63
+ if (!account.apiKey) {
64
+ throw new Error('Account missing Personal Access Token');
65
+ }
66
+ return account.apiKey;
67
+ }
68
+
69
+ /**
70
+ * Fetch usage/quota information from GitHub Models API
71
+ *
72
+ * @param {Object} account - Account object
73
+ * @param {string} token - PAT
74
+ * @returns {Promise<Object>} Quota data
75
+ */
76
+ async getQuotas(account, token) {
77
+ try {
78
+ // Fetch available models from GitHub
79
+ const response = await fetch(this.config.modelsEndpoint, {
80
+ method: 'GET',
81
+ headers: {
82
+ 'Authorization': `Bearer ${token}`,
83
+ 'Accept': 'application/vnd.github+json',
84
+ 'X-GitHub-Api-Version': '2022-11-28'
85
+ }
86
+ });
87
+
88
+ if (!response.ok) {
89
+ throw new Error(`Failed to fetch models: ${response.status}`);
90
+ }
91
+
92
+ const models = await response.json();
93
+ const quotaData = {};
94
+
95
+ // GitHub Models has rate limits per model
96
+ // Default quota assumption since GitHub doesn't expose usage API
97
+ if (Array.isArray(models)) {
98
+ models.forEach(model => {
99
+ quotaData[model.name] = {
100
+ remainingFraction: 1.0, // Unknown - assume available
101
+ resetTime: null
102
+ };
103
+ });
104
+ }
105
+
106
+ // Add common models if list is empty
107
+ if (Object.keys(quotaData).length === 0) {
108
+ const commonModels = [
109
+ 'gpt-4o',
110
+ 'gpt-4o-mini',
111
+ 'claude-3-5-sonnet',
112
+ 'gemini-1.5-pro',
113
+ 'gemini-1.5-flash',
114
+ 'llama-3.1-405b-instruct',
115
+ 'mistral-large'
116
+ ];
117
+ commonModels.forEach(modelId => {
118
+ quotaData[modelId] = {
119
+ remainingFraction: 1.0,
120
+ resetTime: null
121
+ };
122
+ });
123
+ }
124
+
125
+ return { models: quotaData };
126
+ } catch (error) {
127
+ this.error('Failed to fetch quotas', error);
128
+ // Return default quota on error
129
+ return {
130
+ models: {
131
+ 'gpt-4o': { remainingFraction: 1.0, resetTime: null },
132
+ 'claude-3-5-sonnet': { remainingFraction: 1.0, resetTime: null }
133
+ }
134
+ };
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Get subscription tier (GitHub Models is free for developers)
140
+ *
141
+ * @param {Object} account - Account object
142
+ * @param {string} token - PAT
143
+ * @returns {Promise<{tier: string, projectId: null}>}
144
+ */
145
+ async getSubscriptionTier(account, token) {
146
+ try {
147
+ // Check GitHub user/org details
148
+ const response = await fetch('https://api.github.com/user', {
149
+ method: 'GET',
150
+ headers: {
151
+ 'Authorization': `Bearer ${token}`,
152
+ 'Accept': 'application/vnd.github+json',
153
+ 'X-GitHub-Api-Version': '2022-11-28'
154
+ }
155
+ });
156
+
157
+ if (!response.ok) {
158
+ return { tier: 'unknown', projectId: null };
159
+ }
160
+
161
+ const userData = await response.json();
162
+
163
+ // GitHub Copilot subscribers likely have higher rate limits
164
+ // But we can't directly check subscription status via API
165
+ // Default to "developer" tier
166
+ return {
167
+ tier: userData.plan?.name || 'developer',
168
+ projectId: userData.login
169
+ };
170
+ } catch (error) {
171
+ this.error('Failed to fetch subscription tier', error);
172
+ return { tier: 'developer', projectId: null };
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Get available models from GitHub Models
178
+ *
179
+ * @param {Object} account - Account object
180
+ * @param {string} token - PAT
181
+ * @returns {Promise<Array>} List of available models
182
+ */
183
+ async getAvailableModels(account, token) {
184
+ try {
185
+ const response = await fetch(this.config.modelsEndpoint, {
186
+ method: 'GET',
187
+ headers: {
188
+ 'Authorization': `Bearer ${token}`,
189
+ 'Accept': 'application/vnd.github+json',
190
+ 'X-GitHub-Api-Version': '2022-11-28'
191
+ }
192
+ });
193
+
194
+ if (!response.ok) {
195
+ throw new Error(`Failed to fetch models: ${response.status}`);
196
+ }
197
+
198
+ const models = await response.json();
199
+ if (Array.isArray(models)) {
200
+ return models.map(model => {
201
+ // Determine family from model name
202
+ let family = 'unknown';
203
+ if (model.name.includes('gpt')) family = 'gpt';
204
+ else if (model.name.includes('claude')) family = 'claude';
205
+ else if (model.name.includes('gemini')) family = 'gemini';
206
+ else if (model.name.includes('llama')) family = 'llama';
207
+ else if (model.name.includes('mistral')) family = 'mistral';
208
+
209
+ return {
210
+ id: model.name,
211
+ name: model.summary || model.name,
212
+ family
213
+ };
214
+ });
215
+ }
216
+
217
+ return [];
218
+ } catch (error) {
219
+ this.error('Failed to fetch available models', error);
220
+ return [];
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Parse GitHub API rate limit headers
226
+ *
227
+ * @param {Response} response - Fetch response
228
+ * @param {Object} errorData - Error data from response body
229
+ * @returns {Object|null} Rate limit info
230
+ */
231
+ parseRateLimitInfo(response, errorData = null) {
232
+ // GitHub uses these headers:
233
+ // - x-ratelimit-limit
234
+ // - x-ratelimit-remaining
235
+ // - x-ratelimit-reset (Unix timestamp)
236
+ // - x-ratelimit-resource
237
+
238
+ const rateLimitReset = response.headers.get('x-ratelimit-reset');
239
+ const retryAfter = response.headers.get('retry-after');
240
+
241
+ if (rateLimitReset) {
242
+ const resetTimestamp = parseInt(rateLimitReset, 10);
243
+ return {
244
+ resetTime: new Date(resetTimestamp * 1000),
245
+ retryAfter: Math.max(0, Math.floor((resetTimestamp * 1000 - Date.now()) / 1000))
246
+ };
247
+ }
248
+
249
+ if (retryAfter) {
250
+ const retrySeconds = parseInt(retryAfter, 10);
251
+ return {
252
+ resetTime: new Date(Date.now() + retrySeconds * 1000),
253
+ retryAfter: retrySeconds
254
+ };
255
+ }
256
+
257
+ // Check error response
258
+ if (errorData?.message && errorData.message.includes('rate limit')) {
259
+ return {
260
+ resetTime: new Date(Date.now() + 3600000), // Default: 1 hour
261
+ retryAfter: 3600
262
+ };
263
+ }
264
+
265
+ return null;
266
+ }
267
+
268
+ /**
269
+ * Check if error indicates invalid PAT
270
+ *
271
+ * @param {Error} error - Error object
272
+ * @returns {boolean}
273
+ */
274
+ shouldInvalidateCredentials(error) {
275
+ if (error.message && (
276
+ error.message.includes('Bad credentials') ||
277
+ error.message.includes('Requires authentication') ||
278
+ error.message.includes('Invalid token')
279
+ )) {
280
+ return true;
281
+ }
282
+
283
+ return super.shouldInvalidateCredentials(error);
284
+ }
285
+ }
286
+
287
+ export default GitHubProvider;
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Google Cloud Code Provider
3
+ *
4
+ * Implements authentication via Google OAuth with PKCE.
5
+ * Wraps existing OAuth and Cloud Code API logic.
6
+ */
7
+
8
+ import BaseProvider from './base-provider.js';
9
+ import {
10
+ refreshAccessToken,
11
+ getUserEmail,
12
+ discoverProjectId
13
+ } from '../auth/oauth.js';
14
+ import { getModelQuotas, getSubscriptionTier } from '../cloudcode/model-api.js';
15
+ import { logger } from '../utils/logger.js';
16
+
17
+ export class GoogleProvider extends BaseProvider {
18
+ constructor(config = {}) {
19
+ super('google', 'Google Cloud Code', config);
20
+ }
21
+
22
+ /**
23
+ * Validate OAuth refresh token by attempting to refresh
24
+ *
25
+ * @param {Object} account - Account with refreshToken
26
+ * @returns {Promise<{valid: boolean, error?: string, email?: string}>}
27
+ */
28
+ async validateCredentials(account) {
29
+ if (!account.refreshToken) {
30
+ return { valid: false, error: 'Missing refresh token' };
31
+ }
32
+
33
+ try {
34
+ // Try to refresh the access token
35
+ const { accessToken } = await refreshAccessToken(account.refreshToken);
36
+
37
+ // Get email to confirm account identity
38
+ const email = await getUserEmail(accessToken);
39
+
40
+ return { valid: true, email };
41
+ } catch (error) {
42
+ this.error('Credential validation failed', error);
43
+ return { valid: false, error: error.message };
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Get access token from OAuth refresh token
49
+ *
50
+ * @param {Object} account - Account with refreshToken
51
+ * @returns {Promise<string>} Access token
52
+ */
53
+ async getAccessToken(account) {
54
+ if (!account.refreshToken) {
55
+ throw new Error('Account missing refresh token');
56
+ }
57
+
58
+ try {
59
+ const { accessToken } = await refreshAccessToken(account.refreshToken);
60
+ return accessToken;
61
+ } catch (error) {
62
+ this.error('Failed to get access token', error);
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Fetch model quotas from Cloud Code API
69
+ *
70
+ * @param {Object} account - Account object
71
+ * @param {string} token - Access token
72
+ * @returns {Promise<Object>} Quota data
73
+ */
74
+ async getQuotas(account, token) {
75
+ try {
76
+ const projectId = account.projectId || account.subscription?.projectId;
77
+ if (!projectId) {
78
+ this.debug('No project ID available, attempting discovery...');
79
+ const discoveredProject = await discoverProjectId(token);
80
+ if (!discoveredProject) {
81
+ throw new Error('Could not discover project ID');
82
+ }
83
+ return await getModelQuotas(token, discoveredProject);
84
+ }
85
+
86
+ return await getModelQuotas(token, projectId);
87
+ } catch (error) {
88
+ this.error('Failed to fetch quotas', error);
89
+ throw error;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Fetch subscription tier from loadCodeAssist API
95
+ *
96
+ * @param {Object} account - Account object
97
+ * @param {string} token - Access token
98
+ * @returns {Promise<{tier: string, projectId: string}>}
99
+ */
100
+ async getSubscriptionTier(account, token) {
101
+ try {
102
+ return await getSubscriptionTier(token);
103
+ } catch (error) {
104
+ this.error('Failed to fetch subscription tier', error);
105
+ // Return cached value if available
106
+ if (account.subscription?.tier) {
107
+ this.debug('Using cached subscription tier');
108
+ return {
109
+ tier: account.subscription.tier,
110
+ projectId: account.subscription.projectId
111
+ };
112
+ }
113
+ return { tier: 'unknown', projectId: null };
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Refresh OAuth credentials
119
+ *
120
+ * @param {Object} account - Account object
121
+ * @returns {Promise<Object>} Updated account (no changes for OAuth - refresh happens on-demand)
122
+ */
123
+ async refreshCredentials(account) {
124
+ // OAuth refresh happens on-demand via refreshAccessToken()
125
+ // No need to update account object
126
+ return account;
127
+ }
128
+
129
+ /**
130
+ * Parse rate limit headers from Cloud Code API
131
+ *
132
+ * @param {Response} response - Fetch response
133
+ * @param {Object} errorData - Error data from response body
134
+ * @returns {Object|null} Rate limit info
135
+ */
136
+ parseRateLimitInfo(response, errorData = null) {
137
+ // Check for rate limit headers (X-RateLimit-Reset, Retry-After, etc.)
138
+ const retryAfter = response.headers.get('retry-after');
139
+ const rateLimitReset = response.headers.get('x-ratelimit-reset');
140
+
141
+ if (retryAfter) {
142
+ const retrySeconds = parseInt(retryAfter, 10);
143
+ return {
144
+ resetTime: new Date(Date.now() + retrySeconds * 1000),
145
+ retryAfter: retrySeconds
146
+ };
147
+ }
148
+
149
+ if (rateLimitReset) {
150
+ const resetTimestamp = parseInt(rateLimitReset, 10);
151
+ return {
152
+ resetTime: new Date(resetTimestamp * 1000),
153
+ retryAfter: Math.max(0, Math.floor((resetTimestamp * 1000 - Date.now()) / 1000))
154
+ };
155
+ }
156
+
157
+ // Check error data for rate limit info
158
+ if (errorData?.error?.details) {
159
+ const details = errorData.error.details;
160
+ // Look for quota reset time in error details
161
+ if (details.quotaResetTime) {
162
+ return {
163
+ resetTime: new Date(details.quotaResetTime),
164
+ retryAfter: Math.max(0, Math.floor((new Date(details.quotaResetTime) - Date.now()) / 1000))
165
+ };
166
+ }
167
+ }
168
+
169
+ return null;
170
+ }
171
+
172
+ /**
173
+ * Check if error indicates invalid OAuth credentials
174
+ *
175
+ * @param {Error} error - Error object
176
+ * @returns {boolean}
177
+ */
178
+ shouldInvalidateCredentials(error) {
179
+ // Check for OAuth-specific errors
180
+ if (error.message && (
181
+ error.message.includes('invalid_grant') ||
182
+ error.message.includes('Token has been expired or revoked') ||
183
+ error.message.includes('Invalid Credentials')
184
+ )) {
185
+ return true;
186
+ }
187
+
188
+ return super.shouldInvalidateCredentials(error);
189
+ }
190
+ }
191
+
192
+ export default GoogleProvider;
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Provider System for CommonsProxy
3
+ *
4
+ * Multi-provider support inspired by opencode's provider architecture.
5
+ * Allows CommonsProxy to work with multiple AI backends:
6
+ * - Google Cloud Code (OAuth, default)
7
+ * - Anthropic (API key)
8
+ * - OpenAI (API key)
9
+ * - GitHub Models (PAT)
10
+ * - GitHub Copilot
11
+ * - OpenAI-compatible endpoints
12
+ * - Custom providers
13
+ */
14
+
15
+ import { logger } from '../utils/logger.js';
16
+ import GoogleProvider from './google-provider.js';
17
+ import AnthropicProvider from './anthropic-provider.js';
18
+ import OpenAIProvider from './openai-provider.js';
19
+ import GitHubProvider from './github-provider.js';
20
+ import CopilotProvider from './copilot.js';
21
+ import OpenRouterProvider from './openrouter-provider.js';
22
+
23
+ // Provider registry (legacy system for message routing providers)
24
+ const messagingProviders = new Map();
25
+
26
+ // Authentication provider registry (new system)
27
+ const authProviders = new Map();
28
+
29
+ // Initialize authentication providers
30
+ authProviders.set('google', new GoogleProvider());
31
+ authProviders.set('anthropic', new AnthropicProvider());
32
+ authProviders.set('openai', new OpenAIProvider());
33
+ authProviders.set('github', new GitHubProvider());
34
+ authProviders.set('copilot', new CopilotProvider());
35
+ authProviders.set('openrouter', new OpenRouterProvider());
36
+
37
+ /**
38
+ * Provider interface definition
39
+ * Each provider must implement these methods
40
+ */
41
+ export const ProviderInterface = {
42
+ id: 'string', // Unique provider ID
43
+ name: 'string', // Display name
44
+ type: 'string', // Provider type: 'cloudcode', 'copilot', 'openai', 'custom'
45
+
46
+ // Required methods
47
+ // authenticate: async (credentials) => { accessToken, refreshToken, expiresAt }
48
+ // sendMessage: async (request, options) => response
49
+ // sendMessageStream: async function* (request, options) => yields events
50
+ // listModels: async () => [{ id, name, capabilities }]
51
+ };
52
+
53
+ /**
54
+ * Register a legacy messaging provider
55
+ * @param {Object} provider - Provider implementation
56
+ */
57
+ export function registerProvider(provider) {
58
+ if (!provider.id || !provider.name) {
59
+ throw new Error('Provider must have id and name');
60
+ }
61
+ messagingProviders.set(provider.id, provider);
62
+ logger.info(`[Providers] Registered messaging provider: ${provider.name} (${provider.id})`);
63
+ }
64
+
65
+ /**
66
+ * Get a legacy messaging provider by ID
67
+ * @param {string} id - Provider ID
68
+ * @returns {Object|null} Provider or null
69
+ */
70
+ export function getProvider(id) {
71
+ return messagingProviders.get(id) || null;
72
+ }
73
+
74
+ /**
75
+ * Get authentication provider by ID
76
+ * @param {string} providerId - Provider identifier ('google', 'anthropic', 'openai', 'github')
77
+ * @returns {BaseProvider} Provider instance
78
+ */
79
+ export function getAuthProvider(providerId) {
80
+ const provider = authProviders.get(providerId);
81
+ if (!provider) {
82
+ throw new Error(`Unknown auth provider: ${providerId}`);
83
+ }
84
+ return provider;
85
+ }
86
+
87
+ /**
88
+ * Get authentication provider for an account
89
+ * @param {Object} account - Account object with provider field
90
+ * @returns {BaseProvider} Provider instance
91
+ */
92
+ export function getProviderForAccount(account) {
93
+ // Determine provider from account source or explicit provider field
94
+ const providerId = account.provider || detectProviderFromSource(account.source);
95
+ return getAuthProvider(providerId);
96
+ }
97
+
98
+ /**
99
+ * Detect provider from legacy source field
100
+ * @param {string} source - Account source ('oauth', 'manual', 'database')
101
+ * @returns {string} Provider ID
102
+ */
103
+ function detectProviderFromSource(source) {
104
+ // Legacy accounts use 'oauth' or 'database' for Google OAuth
105
+ if (source === 'oauth' || source === 'database') {
106
+ return 'google';
107
+ }
108
+ // Manual accounts default to Google (legacy behavior)
109
+ if (source === 'manual') {
110
+ return 'google';
111
+ }
112
+ // Default to Google
113
+ return 'google';
114
+ }
115
+
116
+ /**
117
+ * Register a custom authentication provider
118
+ * @param {string} id - Provider ID
119
+ * @param {BaseProvider} provider - Provider instance
120
+ */
121
+ export function registerAuthProvider(id, provider) {
122
+ authProviders.set(id, provider);
123
+ logger.info(`[Providers] Registered auth provider: ${provider.name} (${id})`);
124
+ }
125
+
126
+ /**
127
+ * Get list of all available authentication providers
128
+ * @returns {Array<{id: string, name: string, authType: string}>} Provider list
129
+ */
130
+ export function getAllAuthProviders() {
131
+ return Array.from(authProviders.entries()).map(([id, provider]) => ({
132
+ id,
133
+ name: provider.name,
134
+ authType: id === 'google' ? 'oauth' : (id === 'copilot' ? 'device-auth' : 'api-key')
135
+ }));
136
+ }
137
+
138
+ /**
139
+ * List all registered legacy messaging providers
140
+ * @returns {Array} Array of provider info
141
+ */
142
+ export function listProviders() {
143
+ return Array.from(messagingProviders.values()).map(p => ({
144
+ id: p.id,
145
+ name: p.name,
146
+ type: p.type,
147
+ enabled: p.enabled !== false
148
+ }));
149
+ }
150
+
151
+ /**
152
+ * Get the default provider
153
+ * @returns {Object} Default provider (cloudcode)
154
+ */
155
+ export function getDefaultProvider() {
156
+ return messagingProviders.get('cloudcode') || messagingProviders.values().next().value;
157
+ }
158
+
159
+ /**
160
+ * Check if an authentication provider is registered
161
+ * @param {string} providerId - Provider ID to check
162
+ * @returns {boolean} True if provider exists
163
+ */
164
+ export function hasAuthProvider(providerId) {
165
+ return authProviders.has(providerId);
166
+ }
167
+
168
+ /**
169
+ * Provider types enum
170
+ */
171
+ export const ProviderType = {
172
+ CLOUDCODE: 'cloudcode',
173
+ COPILOT: 'copilot',
174
+ OPENROUTER: 'openrouter',
175
+ ANTHROPIC: 'anthropic',
176
+ GITHUB: 'github',
177
+ CUSTOM: 'custom'
178
+ };
179
+
180
+ // Export provider classes for direct instantiation if needed
181
+ export {
182
+ GoogleProvider,
183
+ AnthropicProvider,
184
+ OpenAIProvider,
185
+ GitHubProvider,
186
+ CopilotProvider,
187
+ OpenRouterProvider
188
+ };
189
+
190
+ export default {
191
+ // Legacy messaging provider functions
192
+ registerProvider,
193
+ getProvider,
194
+ listProviders,
195
+ getDefaultProvider,
196
+ // New authentication provider functions
197
+ getAuthProvider,
198
+ getProviderForAccount,
199
+ registerAuthProvider,
200
+ getAllAuthProviders,
201
+ hasAuthProvider,
202
+ // Provider classes
203
+ GoogleProvider,
204
+ AnthropicProvider,
205
+ OpenAIProvider,
206
+ GitHubProvider,
207
+ CopilotProvider,
208
+ OpenRouterProvider,
209
+ // Enums
210
+ ProviderType
211
+ };