liuw15 commited on
Commit
28d241c
·
1 Parent(s): d4f65ed

添加复用id机制,修复潜在的问题,添加npm run refresh刷新所有token的功能

Browse files
package.json CHANGED
@@ -7,6 +7,7 @@
7
  "scripts": {
8
  "start": "node src/server/index.js",
9
  "login": "node scripts/oauth-server.js",
 
10
  "dev": "node --watch src/server/index.js"
11
  },
12
  "keywords": ["antigravity", "openai", "api", "proxy"],
 
7
  "scripts": {
8
  "start": "node src/server/index.js",
9
  "login": "node scripts/oauth-server.js",
10
+ "refresh": "node scripts/refresh-tokens.js",
11
  "dev": "node --watch src/server/index.js"
12
  },
13
  "keywords": ["antigravity", "openai", "api", "proxy"],
scripts/refresh-tokens.js ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import log from '../src/utils/logger.js';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ const ACCOUNTS_FILE = path.join(__dirname, '..', 'data', 'accounts.json');
9
+
10
+ const CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
11
+ const CLIENT_SECRET = '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';
12
+
13
+ async function refreshToken(refreshToken) {
14
+ const body = new URLSearchParams({
15
+ client_id: CLIENT_ID,
16
+ client_secret: CLIENT_SECRET,
17
+ grant_type: 'refresh_token',
18
+ refresh_token: refreshToken
19
+ });
20
+
21
+ const response = await fetch('https://oauth2.googleapis.com/token', {
22
+ method: 'POST',
23
+ headers: {
24
+ 'Host': 'oauth2.googleapis.com',
25
+ 'User-Agent': 'Go-http-client/1.1',
26
+ 'Content-Length': body.toString().length.toString(),
27
+ 'Content-Type': 'application/x-www-form-urlencoded',
28
+ 'Accept-Encoding': 'gzip'
29
+ },
30
+ body: body.toString()
31
+ });
32
+
33
+ if (!response.ok) {
34
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
35
+ }
36
+
37
+ return await response.json();
38
+ }
39
+
40
+ async function refreshAllTokens() {
41
+ if (!fs.existsSync(ACCOUNTS_FILE)) {
42
+ log.error(`文件不存在: ${ACCOUNTS_FILE}`);
43
+ process.exit(1);
44
+ }
45
+
46
+ const accounts = JSON.parse(fs.readFileSync(ACCOUNTS_FILE, 'utf-8'));
47
+ log.info(`找到 ${accounts.length} 个账号`);
48
+
49
+ let successCount = 0;
50
+ let failCount = 0;
51
+
52
+ for (let i = 0; i < accounts.length; i++) {
53
+ const account = accounts[i];
54
+
55
+ if (account.enable === false) {
56
+ log.warn(`账号 ${i + 1}: 已禁用,跳过`);
57
+ continue;
58
+ }
59
+
60
+ try {
61
+ log.info(`刷新账号 ${i + 1}...`);
62
+ const tokenData = await refreshToken(account.refresh_token);
63
+ account.access_token = tokenData.access_token;
64
+ account.expires_in = tokenData.expires_in;
65
+ account.timestamp = Date.now();
66
+
67
+ successCount++;
68
+ log.info(`账号 ${i + 1}: 刷新成功`);
69
+ } catch (error) {
70
+ failCount++;
71
+ log.error(`账号 ${i + 1}: 刷新失败 - ${error.message}`);
72
+
73
+ if (error.message.includes('invalid_grant') || error.message.includes('400')) {
74
+ account.enable = false;
75
+ log.warn(`账号 ${i + 1}: Token 已失效或错误,已自动禁用该账号`);
76
+ }
77
+ }
78
+ }
79
+
80
+ fs.writeFileSync(ACCOUNTS_FILE, JSON.stringify(accounts, null, 2));
81
+ log.info(`刷新完成: 成功 ${successCount} 个, 失败 ${failCount} 个`);
82
+ }
83
+
84
+ refreshAllTokens().catch(err => {
85
+ log.error('刷新失败:', err.message);
86
+ process.exit(1);
87
+ });
src/auth/token_manager.js CHANGED
@@ -2,6 +2,7 @@ import fs from 'fs';
2
  import path from 'path';
3
  import { fileURLToPath } from 'url';
4
  import { log } from '../utils/logger.js';
 
5
 
6
  const __filename = fileURLToPath(import.meta.url);
7
  const __dirname = path.dirname(__filename);
@@ -14,19 +15,36 @@ class TokenManager {
14
  this.filePath = filePath;
15
  this.tokens = [];
16
  this.currentIndex = 0;
17
- this.loadTokens();
18
  }
19
 
20
- loadTokens() {
21
  try {
22
- log.info('正在加载token...');
23
  const data = fs.readFileSync(this.filePath, 'utf8');
24
- const tokenArray = JSON.parse(data);
25
- this.tokens = tokenArray.filter(token => token.enable !== false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  this.currentIndex = 0;
27
  log.info(`成功加载 ${this.tokens.length} 个可用token`);
28
  } catch (error) {
29
- log.error('加载token失败:', error.message);
30
  this.tokens = [];
31
  }
32
  }
@@ -77,7 +95,10 @@ class TokenManager {
77
 
78
  this.tokens.forEach(memToken => {
79
  const index = allTokens.findIndex(t => t.refresh_token === memToken.refresh_token);
80
- if (index !== -1) allTokens[index] = memToken;
 
 
 
81
  });
82
 
83
  fs.writeFileSync(this.filePath, JSON.stringify(allTokens, null, 2), 'utf8');
@@ -90,7 +111,8 @@ class TokenManager {
90
  log.warn(`禁用token`)
91
  token.enable = false;
92
  this.saveToFile();
93
- this.loadTokens();
 
94
  }
95
 
96
  async getToken() {
@@ -106,8 +128,9 @@ class TokenManager {
106
  this.currentIndex = (this.currentIndex + 1) % this.tokens.length;
107
  return token;
108
  } catch (error) {
109
- if (error.statusCode === 403) {
110
- log.warn(`Token ${this.currentIndex} 刷新失败(403),禁用并尝试下一个`);
 
111
  this.disableToken(token);
112
  } else {
113
  log.error(`Token ${this.currentIndex} 刷新失败:`, error.message);
@@ -126,29 +149,6 @@ class TokenManager {
126
  this.disableToken(found);
127
  }
128
  }
129
-
130
- async handleRequestError(error, currentAccessToken) {
131
- if (error.statusCode === 403) {
132
- log.warn('请求遇到403错误,尝试刷新token');
133
- const currentToken = this.tokens[this.currentIndex];
134
- if (currentToken && currentToken.access_token === currentAccessToken) {
135
- try {
136
- await this.refreshToken(currentToken);
137
- log.info('Token刷新成功,返回新token');
138
- return currentToken;
139
- } catch (refreshError) {
140
- if (refreshError.statusCode === 403) {
141
- log.warn('刷新token也遇到403,禁用并切换到下一个');
142
- this.disableToken(currentToken);
143
- return await this.getToken();
144
- }
145
- log.error('刷新token失败:', refreshError.message);
146
- }
147
- }
148
- return await this.getToken();
149
- }
150
- return null;
151
- }
152
  }
153
  const tokenManager = new TokenManager();
154
  export default tokenManager;
 
2
  import path from 'path';
3
  import { fileURLToPath } from 'url';
4
  import { log } from '../utils/logger.js';
5
+ import { generateProjectId, generateSessionId } from '../utils/idGenerator.js';
6
 
7
  const __filename = fileURLToPath(import.meta.url);
8
  const __dirname = path.dirname(__filename);
 
15
  this.filePath = filePath;
16
  this.tokens = [];
17
  this.currentIndex = 0;
18
+ this.initialize();
19
  }
20
 
21
+ initialize() {
22
  try {
23
+ log.info('正在初始化token管理器...');
24
  const data = fs.readFileSync(this.filePath, 'utf8');
25
+ let tokenArray = JSON.parse(data);
26
+ let needSave = false;
27
+
28
+ tokenArray = tokenArray.map(token => {
29
+ if (!token.projectId) {
30
+ token.projectId = generateProjectId();
31
+ needSave = true;
32
+ }
33
+ return token;
34
+ });
35
+
36
+ if (needSave) {
37
+ fs.writeFileSync(this.filePath, JSON.stringify(tokenArray, null, 2), 'utf8');
38
+ }
39
+
40
+ this.tokens = tokenArray.filter(token => token.enable !== false).map(token => ({
41
+ ...token,
42
+ sessionId: generateSessionId()
43
+ }));
44
  this.currentIndex = 0;
45
  log.info(`成功加载 ${this.tokens.length} 个可用token`);
46
  } catch (error) {
47
+ log.error('初始化token失败:', error.message);
48
  this.tokens = [];
49
  }
50
  }
 
95
 
96
  this.tokens.forEach(memToken => {
97
  const index = allTokens.findIndex(t => t.refresh_token === memToken.refresh_token);
98
+ if (index !== -1) {
99
+ const { sessionId, ...tokenToSave } = memToken;
100
+ allTokens[index] = tokenToSave;
101
+ }
102
  });
103
 
104
  fs.writeFileSync(this.filePath, JSON.stringify(allTokens, null, 2), 'utf8');
 
111
  log.warn(`禁用token`)
112
  token.enable = false;
113
  this.saveToFile();
114
+ this.tokens = this.tokens.filter(t => t.refresh_token !== token.refresh_token);
115
+ this.currentIndex = this.currentIndex % Math.max(this.tokens.length, 1);
116
  }
117
 
118
  async getToken() {
 
128
  this.currentIndex = (this.currentIndex + 1) % this.tokens.length;
129
  return token;
130
  } catch (error) {
131
+ if (error.statusCode === 403 || error.statusCode === 400) {
132
+ const accountNum = this.currentIndex + 1;
133
+ log.warn(`账号 ${accountNum}: Token 已失效或错误,已自动禁用该账号`);
134
  this.disableToken(token);
135
  } else {
136
  log.error(`Token ${this.currentIndex} 刷新失败:`, error.message);
 
149
  this.disableToken(found);
150
  }
151
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  }
153
  const tokenManager = new TokenManager();
154
  export default tokenManager;
src/server/index.js CHANGED
@@ -56,7 +56,7 @@ app.post('/v1/chat/completions', async (req, res) => {
56
  return res.status(400).json({ error: 'messages is required' });
57
  }
58
 
59
- const requestBody = generateRequestBody(messages, model, params, tools);
60
  //console.log(JSON.stringify(requestBody,null,2));
61
 
62
  if (stream) {
 
56
  return res.status(400).json({ error: 'messages is required' });
57
  }
58
 
59
+ const requestBody = await generateRequestBody(messages, model, params, tools);
60
  //console.log(JSON.stringify(requestBody,null,2));
61
 
62
  if (stream) {
src/utils/idGenerator.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { randomUUID } from 'crypto';
2
+
3
+ function generateRequestId() {
4
+ return `agent-${randomUUID()}`;
5
+ }
6
+
7
+ function generateSessionId() {
8
+ return String(-Math.floor(Math.random() * 9e18));
9
+ }
10
+
11
+ function generateProjectId() {
12
+ const adjectives = ['useful', 'bright', 'swift', 'calm', 'bold'];
13
+ const nouns = ['fuze', 'wave', 'spark', 'flow', 'core'];
14
+ const randomAdj = adjectives[Math.floor(Math.random() * adjectives.length)];
15
+ const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
16
+ const randomNum = Math.random().toString(36).substring(2, 7);
17
+ return `${randomAdj}-${randomNoun}-${randomNum}`;
18
+ }
19
+ export {
20
+ generateProjectId,
21
+ generateSessionId,
22
+ generateRequestId
23
+ }
src/utils/utils.js CHANGED
@@ -1,22 +1,7 @@
1
- import { randomUUID } from 'crypto';
2
  import config from '../config/config.js';
 
 
3
 
4
- function generateRequestId() {
5
- return `agent-${randomUUID()}`;
6
- }
7
-
8
- function generateSessionId() {
9
- return String(-Math.floor(Math.random() * 9e18));
10
- }
11
-
12
- function generateProjectId() {
13
- const adjectives = ['useful', 'bright', 'swift', 'calm', 'bold'];
14
- const nouns = ['fuze', 'wave', 'spark', 'flow', 'core'];
15
- const randomAdj = adjectives[Math.floor(Math.random() * adjectives.length)];
16
- const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
17
- const randomNum = Math.random().toString(36).substring(2, 7);
18
- return `${randomAdj}-${randomNoun}-${randomNum}`;
19
- }
20
  function extractImagesFromContent(content) {
21
  const result = { text: '', images: [] };
22
 
@@ -183,7 +168,12 @@ function convertOpenAIToolsToAntigravity(openaiTools){
183
  }
184
  })
185
  }
186
- function generateRequestBody(openaiMessages,modelName,parameters,openaiTools){
 
 
 
 
 
187
  const enableThinking = modelName.endsWith('-thinking') ||
188
  modelName === 'gemini-2.5-pro' ||
189
  modelName.startsWith('gemini-3-pro-') ||
@@ -192,7 +182,7 @@ function generateRequestBody(openaiMessages,modelName,parameters,openaiTools){
192
  const actualModelName = modelName.endsWith('-thinking') ? modelName.slice(0, -9) : modelName;
193
 
194
  return{
195
- project: generateProjectId(),
196
  requestId: generateRequestId(),
197
  request: {
198
  contents: openaiMessageToAntigravity(openaiMessages),
@@ -207,7 +197,7 @@ function generateRequestBody(openaiMessages,modelName,parameters,openaiTools){
207
  }
208
  },
209
  generationConfig: generateGenerationConfig(parameters, enableThinking, actualModelName),
210
- sessionId: generateSessionId()
211
  },
212
  model: actualModelName,
213
  userAgent: "antigravity"
@@ -215,7 +205,5 @@ function generateRequestBody(openaiMessages,modelName,parameters,openaiTools){
215
  }
216
  export{
217
  generateRequestId,
218
- generateSessionId,
219
- generateProjectId,
220
  generateRequestBody
221
  }
 
 
1
  import config from '../config/config.js';
2
+ import tokenManager from '../auth/token_manager.js';
3
+ import { generateRequestId } from './idGenerator.js';
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  function extractImagesFromContent(content) {
6
  const result = { text: '', images: [] };
7
 
 
168
  }
169
  })
170
  }
171
+ async function generateRequestBody(openaiMessages,modelName,parameters,openaiTools){
172
+ const token = await tokenManager.getToken();
173
+ if (!token) {
174
+ throw new Error('没有可用的token,请运行 npm run login 获取token');
175
+ }
176
+
177
  const enableThinking = modelName.endsWith('-thinking') ||
178
  modelName === 'gemini-2.5-pro' ||
179
  modelName.startsWith('gemini-3-pro-') ||
 
182
  const actualModelName = modelName.endsWith('-thinking') ? modelName.slice(0, -9) : modelName;
183
 
184
  return{
185
+ project: token.projectId,
186
  requestId: generateRequestId(),
187
  request: {
188
  contents: openaiMessageToAntigravity(openaiMessages),
 
197
  }
198
  },
199
  generationConfig: generateGenerationConfig(parameters, enableThinking, actualModelName),
200
+ sessionId: token.sessionId
201
  },
202
  model: actualModelName,
203
  userAgent: "antigravity"
 
205
  }
206
  export{
207
  generateRequestId,
 
 
208
  generateRequestBody
209
  }