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.
- package/README.md +180 -0
- package/config/constants.js +206 -0
- package/config/defaultThinkingSignature.js +7 -0
- package/config/ollamaModels.js +19 -0
- package/config/providerModels.js +161 -0
- package/handlers/chatCore.js +277 -0
- package/handlers/responsesHandler.js +69 -0
- package/index.js +69 -0
- package/package.json +44 -0
- package/services/accountFallback.js +148 -0
- package/services/combo.js +69 -0
- package/services/compact.js +64 -0
- package/services/model.js +109 -0
- package/services/provider.js +237 -0
- package/services/tokenRefresh.js +542 -0
- package/services/usage.js +398 -0
- package/translator/formats.js +12 -0
- package/translator/from-openai/claude.js +341 -0
- package/translator/from-openai/gemini.js +469 -0
- package/translator/from-openai/openai-responses.js +361 -0
- package/translator/helpers/claudeHelper.js +179 -0
- package/translator/helpers/geminiHelper.js +131 -0
- package/translator/helpers/openaiHelper.js +80 -0
- package/translator/helpers/responsesApiHelper.js +103 -0
- package/translator/helpers/toolCallHelper.js +111 -0
- package/translator/index.js +167 -0
- package/translator/to-openai/claude.js +238 -0
- package/translator/to-openai/gemini.js +151 -0
- package/translator/to-openai/openai-responses.js +140 -0
- package/translator/to-openai/openai.js +371 -0
- package/utils/bypassHandler.js +258 -0
- package/utils/error.js +133 -0
- package/utils/ollamaTransform.js +82 -0
- package/utils/requestLogger.js +217 -0
- package/utils/stream.js +274 -0
- 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
|
+
|