open-sse 1.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 (36) hide show
  1. package/README.md +180 -0
  2. package/config/constants.js +206 -0
  3. package/config/defaultThinkingSignature.js +7 -0
  4. package/config/ollamaModels.js +19 -0
  5. package/config/providerModels.js +161 -0
  6. package/handlers/chatCore.js +277 -0
  7. package/handlers/responsesHandler.js +69 -0
  8. package/index.js +69 -0
  9. package/package.json +44 -0
  10. package/services/accountFallback.js +148 -0
  11. package/services/combo.js +69 -0
  12. package/services/compact.js +64 -0
  13. package/services/model.js +109 -0
  14. package/services/provider.js +237 -0
  15. package/services/tokenRefresh.js +542 -0
  16. package/services/usage.js +398 -0
  17. package/translator/formats.js +12 -0
  18. package/translator/from-openai/claude.js +341 -0
  19. package/translator/from-openai/gemini.js +469 -0
  20. package/translator/from-openai/openai-responses.js +361 -0
  21. package/translator/helpers/claudeHelper.js +179 -0
  22. package/translator/helpers/geminiHelper.js +131 -0
  23. package/translator/helpers/openaiHelper.js +80 -0
  24. package/translator/helpers/responsesApiHelper.js +103 -0
  25. package/translator/helpers/toolCallHelper.js +111 -0
  26. package/translator/index.js +167 -0
  27. package/translator/to-openai/claude.js +238 -0
  28. package/translator/to-openai/gemini.js +151 -0
  29. package/translator/to-openai/openai-responses.js +140 -0
  30. package/translator/to-openai/openai.js +371 -0
  31. package/utils/bypassHandler.js +258 -0
  32. package/utils/error.js +133 -0
  33. package/utils/ollamaTransform.js +82 -0
  34. package/utils/requestLogger.js +217 -0
  35. package/utils/stream.js +274 -0
  36. package/utils/streamHandler.js +131 -0
@@ -0,0 +1,542 @@
1
+ import { PROVIDERS, OAUTH_ENDPOINTS } from "../config/constants.js";
2
+
3
+ // Token expiry buffer (refresh if expires within 5 minutes)
4
+ export const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
5
+
6
+ /**
7
+ * Refresh OAuth access token using refresh token
8
+ */
9
+ export async function refreshAccessToken(provider, refreshToken, credentials, log) {
10
+ const config = PROVIDERS[provider];
11
+
12
+ if (!config || !config.refreshUrl) {
13
+ log?.warn?.("TOKEN_REFRESH", `No refresh URL configured for provider: ${provider}`);
14
+ return null;
15
+ }
16
+
17
+ if (!refreshToken) {
18
+ log?.warn?.("TOKEN_REFRESH", `No refresh token available for provider: ${provider}`);
19
+ return null;
20
+ }
21
+
22
+ try {
23
+ const response = await fetch(config.refreshUrl, {
24
+ method: "POST",
25
+ headers: {
26
+ "Content-Type": "application/x-www-form-urlencoded",
27
+ Accept: "application/json",
28
+ },
29
+ body: new URLSearchParams({
30
+ grant_type: "refresh_token",
31
+ refresh_token: refreshToken,
32
+ client_id: config.clientId,
33
+ client_secret: config.clientSecret,
34
+ }),
35
+ });
36
+
37
+ if (!response.ok) {
38
+ const errorText = await response.text();
39
+ log?.error?.("TOKEN_REFRESH", `Failed to refresh token for ${provider}`, {
40
+ status: response.status,
41
+ error: errorText,
42
+ });
43
+ return null;
44
+ }
45
+
46
+ const tokens = await response.json();
47
+
48
+ log?.info?.("TOKEN_REFRESH", `Successfully refreshed token for ${provider}`, {
49
+ hasNewAccessToken: !!tokens.access_token,
50
+ hasNewRefreshToken: !!tokens.refresh_token,
51
+ expiresIn: tokens.expires_in,
52
+ });
53
+
54
+ return {
55
+ accessToken: tokens.access_token,
56
+ refreshToken: tokens.refresh_token || refreshToken,
57
+ expiresIn: tokens.expires_in,
58
+ };
59
+ } catch (error) {
60
+ log?.error?.("TOKEN_REFRESH", `Error refreshing token for ${provider}`, {
61
+ error: error.message,
62
+ });
63
+ return null;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Specialized refresh for Claude OAuth tokens
69
+ */
70
+ export async function refreshClaudeOAuthToken(refreshToken, log) {
71
+ const response = await fetch(OAUTH_ENDPOINTS.anthropic.token, {
72
+ method: "POST",
73
+ headers: {
74
+ "Content-Type": "application/json",
75
+ Accept: "application/json",
76
+ },
77
+ body: JSON.stringify({
78
+ grant_type: "refresh_token",
79
+ refresh_token: refreshToken,
80
+ client_id: PROVIDERS.claude.clientId,
81
+ }),
82
+ });
83
+
84
+ if (!response.ok) {
85
+ const errorText = await response.text();
86
+ log?.error?.("TOKEN_REFRESH", "Failed to refresh Claude OAuth token", {
87
+ status: response.status,
88
+ error: errorText,
89
+ });
90
+ return null;
91
+ }
92
+
93
+ const tokens = await response.json();
94
+
95
+ log?.info?.("TOKEN_REFRESH", "Successfully refreshed Claude OAuth token", {
96
+ hasNewAccessToken: !!tokens.access_token,
97
+ hasNewRefreshToken: !!tokens.refresh_token,
98
+ expiresIn: tokens.expires_in,
99
+ });
100
+
101
+ return {
102
+ accessToken: tokens.access_token,
103
+ refreshToken: tokens.refresh_token || refreshToken,
104
+ expiresIn: tokens.expires_in,
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Specialized refresh for Google providers (Gemini, Antigravity)
110
+ */
111
+ export async function refreshGoogleToken(refreshToken, clientId, clientSecret, log) {
112
+ const response = await fetch(OAUTH_ENDPOINTS.google.token, {
113
+ method: "POST",
114
+ headers: {
115
+ "Content-Type": "application/x-www-form-urlencoded",
116
+ Accept: "application/json",
117
+ },
118
+ body: new URLSearchParams({
119
+ grant_type: "refresh_token",
120
+ refresh_token: refreshToken,
121
+ client_id: clientId,
122
+ client_secret: clientSecret,
123
+ }),
124
+ });
125
+
126
+ if (!response.ok) {
127
+ const errorText = await response.text();
128
+ log?.error?.("TOKEN_REFRESH", "Failed to refresh Google token", {
129
+ status: response.status,
130
+ error: errorText,
131
+ });
132
+ return null;
133
+ }
134
+
135
+ const tokens = await response.json();
136
+
137
+ log?.info?.("TOKEN_REFRESH", "Successfully refreshed Google token", {
138
+ hasNewAccessToken: !!tokens.access_token,
139
+ hasNewRefreshToken: !!tokens.refresh_token,
140
+ expiresIn: tokens.expires_in,
141
+ });
142
+
143
+ return {
144
+ accessToken: tokens.access_token,
145
+ refreshToken: tokens.refresh_token || refreshToken,
146
+ expiresIn: tokens.expires_in,
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Specialized refresh for Qwen OAuth tokens
152
+ */
153
+ export async function refreshQwenToken(refreshToken, log) {
154
+ const endpoint = OAUTH_ENDPOINTS.qwen.token;
155
+
156
+ try {
157
+ const response = await fetch(endpoint, {
158
+ method: "POST",
159
+ headers: {
160
+ "Content-Type": "application/x-www-form-urlencoded",
161
+ Accept: "application/json",
162
+ },
163
+ body: new URLSearchParams({
164
+ grant_type: "refresh_token",
165
+ refresh_token: refreshToken,
166
+ client_id: PROVIDERS.qwen.clientId,
167
+ }),
168
+ });
169
+
170
+ if (response.status === 200) {
171
+ const tokens = await response.json();
172
+
173
+ log?.info?.("TOKEN_REFRESH", "Successfully refreshed Qwen token", {
174
+ hasNewAccessToken: !!tokens.access_token,
175
+ hasNewRefreshToken: !!tokens.refresh_token,
176
+ expiresIn: tokens.expires_in,
177
+ });
178
+
179
+ return {
180
+ accessToken: tokens.access_token,
181
+ refreshToken: tokens.refresh_token || refreshToken,
182
+ expiresIn: tokens.expires_in,
183
+ };
184
+ } else {
185
+ const errorText = await response.text().catch(() => "");
186
+ log?.warn?.("TOKEN_REFRESH", `Error with Qwen endpoint`, {
187
+ status: response.status,
188
+ error: errorText,
189
+ });
190
+ }
191
+ } catch (error) {
192
+ log?.warn?.("TOKEN_REFRESH", `Network error trying Qwen endpoint`, {
193
+ error: error.message,
194
+ });
195
+ }
196
+
197
+ log?.error?.("TOKEN_REFRESH", "Failed to refresh Qwen token");
198
+ return null;
199
+ }
200
+
201
+ /**
202
+ * Specialized refresh for Codex (OpenAI) OAuth tokens
203
+ */
204
+ export async function refreshCodexToken(refreshToken, log) {
205
+ const response = await fetch(OAUTH_ENDPOINTS.openai.token, {
206
+ method: "POST",
207
+ headers: {
208
+ "Content-Type": "application/x-www-form-urlencoded",
209
+ Accept: "application/json",
210
+ },
211
+ body: new URLSearchParams({
212
+ grant_type: "refresh_token",
213
+ refresh_token: refreshToken,
214
+ client_id: PROVIDERS.codex.clientId,
215
+ scope: "openid profile email offline_access",
216
+ }),
217
+ });
218
+
219
+ if (!response.ok) {
220
+ const errorText = await response.text();
221
+ log?.error?.("TOKEN_REFRESH", "Failed to refresh Codex token", {
222
+ status: response.status,
223
+ error: errorText,
224
+ });
225
+ return null;
226
+ }
227
+
228
+ const tokens = await response.json();
229
+
230
+ log?.info?.("TOKEN_REFRESH", "Successfully refreshed Codex token", {
231
+ hasNewAccessToken: !!tokens.access_token,
232
+ hasNewRefreshToken: !!tokens.refresh_token,
233
+ expiresIn: tokens.expires_in,
234
+ });
235
+
236
+ return {
237
+ accessToken: tokens.access_token,
238
+ refreshToken: tokens.refresh_token || refreshToken,
239
+ expiresIn: tokens.expires_in,
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Specialized refresh for iFlow OAuth tokens
245
+ */
246
+ export async function refreshIflowToken(refreshToken, log) {
247
+ const basicAuth = btoa(`${PROVIDERS.iflow.clientId}:${PROVIDERS.iflow.clientSecret}`);
248
+
249
+ const response = await fetch(OAUTH_ENDPOINTS.iflow.token, {
250
+ method: "POST",
251
+ headers: {
252
+ "Content-Type": "application/x-www-form-urlencoded",
253
+ Accept: "application/json",
254
+ Authorization: `Basic ${basicAuth}`,
255
+ },
256
+ body: new URLSearchParams({
257
+ grant_type: "refresh_token",
258
+ refresh_token: refreshToken,
259
+ client_id: PROVIDERS.iflow.clientId,
260
+ client_secret: PROVIDERS.iflow.clientSecret,
261
+ }),
262
+ });
263
+
264
+ if (!response.ok) {
265
+ const errorText = await response.text();
266
+ log?.error?.("TOKEN_REFRESH", "Failed to refresh iFlow token", {
267
+ status: response.status,
268
+ error: errorText,
269
+ });
270
+ return null;
271
+ }
272
+
273
+ const tokens = await response.json();
274
+
275
+ log?.info?.("TOKEN_REFRESH", "Successfully refreshed iFlow token", {
276
+ hasNewAccessToken: !!tokens.access_token,
277
+ hasNewRefreshToken: !!tokens.refresh_token,
278
+ expiresIn: tokens.expires_in,
279
+ });
280
+
281
+ return {
282
+ accessToken: tokens.access_token,
283
+ refreshToken: tokens.refresh_token || refreshToken,
284
+ expiresIn: tokens.expires_in,
285
+ };
286
+ }
287
+
288
+ /**
289
+ * Specialized refresh for GitHub Copilot OAuth tokens
290
+ */
291
+ export async function refreshGitHubToken(refreshToken, log) {
292
+ const response = await fetch(OAUTH_ENDPOINTS.github.token, {
293
+ method: "POST",
294
+ headers: {
295
+ "Content-Type": "application/x-www-form-urlencoded",
296
+ Accept: "application/json",
297
+ },
298
+ body: new URLSearchParams({
299
+ grant_type: "refresh_token",
300
+ refresh_token: refreshToken,
301
+ client_id: PROVIDERS.github.clientId,
302
+ client_secret: PROVIDERS.github.clientSecret,
303
+ }),
304
+ });
305
+
306
+ if (!response.ok) {
307
+ const errorText = await response.text();
308
+ log?.error?.("TOKEN_REFRESH", "Failed to refresh GitHub token", {
309
+ status: response.status,
310
+ error: errorText,
311
+ });
312
+ return null;
313
+ }
314
+
315
+ const tokens = await response.json();
316
+
317
+ log?.info?.("TOKEN_REFRESH", "Successfully refreshed GitHub token", {
318
+ hasNewAccessToken: !!tokens.access_token,
319
+ hasNewRefreshToken: !!tokens.refresh_token,
320
+ expiresIn: tokens.expires_in,
321
+ });
322
+
323
+ return {
324
+ accessToken: tokens.access_token,
325
+ refreshToken: tokens.refresh_token || refreshToken,
326
+ expiresIn: tokens.expires_in,
327
+ };
328
+ }
329
+
330
+ /**
331
+ * Refresh GitHub Copilot token using GitHub access token
332
+ */
333
+ export async function refreshCopilotToken(githubAccessToken, log) {
334
+ try {
335
+ const response = await fetch("https://api.github.com/copilot_internal/v2/token", {
336
+ headers: {
337
+ "Authorization": `Bearer ${githubAccessToken}`,
338
+ "User-Agent": "GitHub-Copilot/1.0",
339
+ "Accept": "*/*"
340
+ }
341
+ });
342
+
343
+ if (!response.ok) {
344
+ const errorText = await response.text();
345
+ log?.error?.("TOKEN_REFRESH", "Failed to refresh Copilot token", {
346
+ status: response.status,
347
+ error: errorText
348
+ });
349
+ return null;
350
+ }
351
+
352
+ const data = await response.json();
353
+
354
+ log?.info?.("TOKEN_REFRESH", "Successfully refreshed Copilot token", {
355
+ hasToken: !!data.token,
356
+ expiresAt: data.expires_at
357
+ });
358
+
359
+ return {
360
+ token: data.token,
361
+ expiresAt: data.expires_at
362
+ };
363
+ } catch (error) {
364
+ log?.error?.("TOKEN_REFRESH", "Error refreshing Copilot token", {
365
+ error: error.message
366
+ });
367
+ return null;
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Get access token for a specific provider
373
+ */
374
+ export async function getAccessToken(provider, credentials, log) {
375
+ if (!credentials || !credentials.refreshToken) {
376
+ log?.warn?.("TOKEN_REFRESH", `No refresh token available for provider: ${provider}`);
377
+ return null;
378
+ }
379
+
380
+ switch (provider) {
381
+ case "gemini":
382
+ case "gemini-cli":
383
+ case "antigravity":
384
+ return await refreshGoogleToken(
385
+ credentials.refreshToken,
386
+ PROVIDERS[provider].clientId,
387
+ PROVIDERS[provider].clientSecret,
388
+ log
389
+ );
390
+
391
+ case "claude":
392
+ return await refreshClaudeOAuthToken(credentials.refreshToken, log);
393
+
394
+ case "codex":
395
+ return await refreshCodexToken(credentials.refreshToken, log);
396
+
397
+ case "qwen":
398
+ return await refreshQwenToken(credentials.refreshToken, log);
399
+
400
+ case "iflow":
401
+ return await refreshIflowToken(credentials.refreshToken, log);
402
+
403
+ case "github":
404
+ return await refreshGitHubToken(credentials.refreshToken, log);
405
+
406
+ default:
407
+ log?.warn?.("TOKEN_REFRESH", `Unsupported provider for token refresh: ${provider}`);
408
+ return null;
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Refresh token by provider type (helper for handlers)
414
+ */
415
+ export async function refreshTokenByProvider(provider, credentials, log) {
416
+ if (!credentials.refreshToken) return null;
417
+
418
+ switch (provider) {
419
+ case "gemini-cli":
420
+ case "antigravity":
421
+ return refreshGoogleToken(
422
+ credentials.refreshToken,
423
+ PROVIDERS[provider].clientId,
424
+ PROVIDERS[provider].clientSecret,
425
+ log
426
+ );
427
+ case "claude":
428
+ return refreshClaudeOAuthToken(credentials.refreshToken, log);
429
+ case "codex":
430
+ return refreshCodexToken(credentials.refreshToken, log);
431
+ case "qwen":
432
+ return refreshQwenToken(credentials.refreshToken, log);
433
+ case "iflow":
434
+ return refreshIflowToken(credentials.refreshToken, log);
435
+ case "github":
436
+ return refreshGitHubToken(credentials.refreshToken, log);
437
+ default:
438
+ return refreshAccessToken(provider, credentials.refreshToken, credentials, log);
439
+ }
440
+ }
441
+
442
+ /**
443
+ * Format credentials for provider
444
+ */
445
+ export function formatProviderCredentials(provider, credentials, log) {
446
+ const config = PROVIDERS[provider];
447
+ if (!config) {
448
+ log?.warn?.("TOKEN_REFRESH", `No configuration found for provider: ${provider}`);
449
+ return null;
450
+ }
451
+
452
+ switch (provider) {
453
+ case "gemini":
454
+ return {
455
+ apiKey: credentials.apiKey,
456
+ accessToken: credentials.accessToken,
457
+ projectId: credentials.projectId
458
+ };
459
+
460
+ case "claude":
461
+ return {
462
+ apiKey: credentials.apiKey,
463
+ accessToken: credentials.accessToken
464
+ };
465
+
466
+ case "codex":
467
+ case "qwen":
468
+ case "iflow":
469
+ case "openai":
470
+ case "openrouter":
471
+ return {
472
+ apiKey: credentials.apiKey,
473
+ accessToken: credentials.accessToken
474
+ };
475
+
476
+ case "antigravity":
477
+ case "gemini-cli":
478
+ return {
479
+ accessToken: credentials.accessToken,
480
+ refreshToken: credentials.refreshToken
481
+ };
482
+
483
+ default:
484
+ return {
485
+ apiKey: credentials.apiKey,
486
+ accessToken: credentials.accessToken,
487
+ refreshToken: credentials.refreshToken
488
+ };
489
+ }
490
+ }
491
+
492
+ /**
493
+ * Get all access tokens for a user
494
+ */
495
+ export async function getAllAccessTokens(userInfo, log) {
496
+ const results = {};
497
+
498
+ if (userInfo.connections && Array.isArray(userInfo.connections)) {
499
+ for (const connection of userInfo.connections) {
500
+ if (connection.isActive && connection.provider) {
501
+ const token = await getAccessToken(connection.provider, {
502
+ refreshToken: connection.refreshToken
503
+ }, log);
504
+
505
+ if (token) {
506
+ results[connection.provider] = token;
507
+ }
508
+ }
509
+ }
510
+ }
511
+
512
+ return results;
513
+ }
514
+
515
+ /**
516
+ * Refresh token with retry and exponential backoff
517
+ * Retries on failure with increasing delay: 1s, 2s, 3s...
518
+ * @param {function} refreshFn - Async function that returns token or null
519
+ * @param {number} maxRetries - Max retry attempts (default 3)
520
+ * @param {object} log - Logger instance (optional)
521
+ * @returns {Promise<object|null>} Token result or null if all retries fail
522
+ */
523
+ export async function refreshWithRetry(refreshFn, maxRetries = 3, log = null) {
524
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
525
+ if (attempt > 0) {
526
+ const delay = attempt * 1000;
527
+ log?.debug?.("TOKEN_REFRESH", `Retry ${attempt}/${maxRetries} after ${delay}ms`);
528
+ await new Promise(r => setTimeout(r, delay));
529
+ }
530
+
531
+ try {
532
+ const result = await refreshFn();
533
+ if (result) return result;
534
+ } catch (error) {
535
+ log?.warn?.("TOKEN_REFRESH", `Attempt ${attempt + 1}/${maxRetries} failed: ${error.message}`);
536
+ }
537
+ }
538
+
539
+ log?.error?.("TOKEN_REFRESH", `All ${maxRetries} retry attempts failed`);
540
+ return null;
541
+ }
542
+