liuw15 commited on
Commit
10feaec
·
0 Parent(s):

first commit

Browse files
Files changed (13) hide show
  1. .gitattributes +4 -0
  2. .gitignore +17 -0
  3. README.md +155 -0
  4. api.js +90 -0
  5. config.js +26 -0
  6. config.json +23 -0
  7. logger.js +28 -0
  8. oauth-server.js +140 -0
  9. package-lock.json +819 -0
  10. package.json +21 -0
  11. server.js +142 -0
  12. token_manager.js +147 -0
  13. utils.js +102 -0
.gitattributes ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ *.exe filter=lfs diff=lfs merge=lfs -text
2
+ *.jpg filter=lfs diff=lfs merge=lfs -text
3
+ src/bin/antigravity_requester_android_arm64 filter=lfs diff=lfs merge=lfs -text
4
+ src/bin/antigravity_requester_linux_amd64 filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Node.js
2
+ node_modules/
3
+
4
+ # 账户信息
5
+ # accounts.json
6
+
7
+ # 日志
8
+ *.log
9
+ logs/
10
+
11
+ # 环境变量
12
+ .env
13
+ .env.local
14
+
15
+ # 系统文件
16
+ .DS_Store
17
+ Thumbs.db
README.md ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Antigravity to OpenAI API 代理服务
2
+
3
+ 将 Google Antigravity API 转换为 OpenAI 兼容格式的代理服务,支持流式和非流式响应。
4
+
5
+ ## 功能特性
6
+
7
+ - ✅ OpenAI API 兼容格式
8
+ - ✅ 支持流式和非流式响应
9
+ - ✅ 自动 Token 管理和刷新
10
+ - ✅ 多账户支持
11
+ - ✅ 思维链输出(thinking)
12
+ - ✅ 完整的日志记录
13
+
14
+ ## 快速开始
15
+
16
+ ### 安装依赖
17
+
18
+ ```bash
19
+ npm install
20
+ ```
21
+
22
+ ### 配置
23
+
24
+ 编辑 `config.json` 配置文件:
25
+
26
+ ```json
27
+ {
28
+ "server": {
29
+ "port": 8045,
30
+ "host": "0.0.0.0"
31
+ },
32
+ "api": {
33
+ "url": "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:streamGenerateContent?alt=sse",
34
+ "modelsUrl": "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels",
35
+ "host": "daily-cloudcode-pa.sandbox.googleapis.com",
36
+ "userAgent": "antigravity/1.11.3 windows/amd64"
37
+ },
38
+ "defaults": {
39
+ "temperature": 1,
40
+ "top_p": 0.85,
41
+ "top_k": 100,
42
+ "max_tokens": 8096
43
+ },
44
+ "security": {
45
+ "maxRequestSize": "50mb",
46
+ "apiKey": "your-api-key-here"
47
+ }
48
+ }
49
+ ```
50
+
51
+ 配置说明:
52
+ - `apiKey`: 设置 API Key 以保护 /v1 端点,留空或设为 null 则不启用验证
53
+
54
+ ### 账户登录
55
+
56
+ 运行 OAuth 服务器进行账户授权:
57
+
58
+ ```bash
59
+ npm run login
60
+ ```
61
+
62
+ 按照提示完成 Google 账户授权,账户信息将保存在 `accounts.json`。
63
+
64
+ ### 启动服务
65
+
66
+ ```bash
67
+ npm start
68
+ ```
69
+
70
+ 开发模式(自动重启):
71
+
72
+ ```bash
73
+ npm run dev
74
+ ```
75
+
76
+ ## API 端点
77
+
78
+ ### 获取可用模型
79
+
80
+ ```bash
81
+ GET http://localhost:8045/v1/models
82
+ ```
83
+
84
+ ### 聊天补全
85
+
86
+ ```bash
87
+ POST http://localhost:8045/v1/chat/completions
88
+ Content-Type: application/json
89
+
90
+ {
91
+ "model": "claude-sonnet-4-5-thinking",
92
+ "messages": [
93
+ {"role": "user", "content": "Hello"}
94
+ ],
95
+ "stream": true
96
+ }
97
+ ```
98
+
99
+ ## 使用示例
100
+
101
+ ### cURL
102
+
103
+ ```bash
104
+ curl http://localhost:8045/v1/chat/completions \
105
+ -H "Content-Type: application/json" \
106
+ -H "Authorization: Bearer your-api-key-here" \
107
+ -d '{
108
+ "model": "claude-sonnet-4-5-thinking",
109
+ "messages": [{"role": "user", "content": "你好"}],
110
+ "stream": true
111
+ }'
112
+ ```
113
+
114
+ ### Python
115
+
116
+ ```python
117
+ import openai
118
+
119
+ client = openai.OpenAI(
120
+ base_url="http://localhost:8045/v1",
121
+ api_key="your-api-key-here"
122
+ )
123
+
124
+ response = client.chat.completions.create(
125
+ model="claude-sonnet-4-5-thinking",
126
+ messages=[{"role": "user", "content": "你好"}],
127
+ stream=True
128
+ )
129
+
130
+ for chunk in response:
131
+ print(chunk.choices[0].delta.content, end="")
132
+ ```
133
+
134
+ ## 项目结构
135
+
136
+ ```
137
+ .
138
+ ├── server.js # 主服务器
139
+ ├── api.js # API 调用逻辑
140
+ ├── oauth-server.js # OAuth 授权服务器
141
+ ├── token_manager.js # Token 管理
142
+ ├── utils.js # 工具函数
143
+ ├── logger.js # 日志模块
144
+ ├── config.json # 配置文件
145
+ ├── accounts.json # 账户信息(自动生成)
146
+ └── package.json # 项目配置
147
+ ```
148
+
149
+ ## 环境要求
150
+
151
+ - Node.js >= 18.0.0
152
+
153
+ ## 许可证
154
+
155
+ MIT
api.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tokenManager from './token_manager.js';
2
+ import { generateRequestBody } from './utils.js';
3
+ import config from './config.js';
4
+
5
+ export async function generateAssistantResponse(requestBody, callback) {
6
+ const token = await tokenManager.getToken();
7
+ const url = config.api.url;
8
+
9
+ const response = await fetch(url, {
10
+ method: 'POST',
11
+ headers: {
12
+ 'Host': config.api.host,
13
+ 'User-Agent': config.api.userAgent,
14
+ 'Authorization': `Bearer ${token}`,
15
+ 'Content-Type': 'application/json',
16
+ 'Accept-Encoding': 'gzip'
17
+ },
18
+ body: JSON.stringify(requestBody)
19
+ });
20
+
21
+ if (!response.ok) {
22
+ const errorText = await response.text();
23
+ throw new Error(`API请求失败 (${response.status}): ${errorText}`);
24
+ }
25
+
26
+ const reader = response.body.getReader();
27
+ const decoder = new TextDecoder();
28
+ let thinkingStarted = false;
29
+
30
+ while (true) {
31
+ const { done, value } = await reader.read();
32
+ if (done) break;
33
+
34
+ const chunk = decoder.decode(value);
35
+ const lines = chunk.split('\n').filter(line => line.startsWith('data: '));
36
+
37
+ for (const line of lines) {
38
+ const jsonStr = line.slice(6);
39
+ try {
40
+ const data = JSON.parse(jsonStr);
41
+ const parts = data.response?.candidates?.[0]?.content?.parts?.[0];
42
+ if (parts) {
43
+ if (parts.thought === true) {
44
+ if (!thinkingStarted) {
45
+ callback('<think>\n');
46
+ thinkingStarted = true;
47
+ }
48
+ callback(parts.text || '');
49
+ } else if (parts.text !== undefined) {
50
+ if (thinkingStarted) {
51
+ callback('\n</think>\n');
52
+ thinkingStarted = false;
53
+ }
54
+ callback(parts.text);
55
+ }
56
+ }
57
+ } catch (e) {
58
+ // 忽略解析错误
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ export async function getAvailableModels() {
65
+ const token = await tokenManager.getToken();
66
+
67
+ const response = await fetch(config.api.modelsUrl, {
68
+ method: 'POST',
69
+ headers: {
70
+ 'Host': config.api.host,
71
+ 'User-Agent': config.api.userAgent,
72
+ 'Authorization': `Bearer ${token}`,
73
+ 'Content-Type': 'application/json',
74
+ 'Accept-Encoding': 'gzip'
75
+ },
76
+ body: JSON.stringify({})
77
+ });
78
+
79
+ const data = await response.json();
80
+
81
+ return {
82
+ object: 'list',
83
+ data: Object.keys(data.models).map(id => ({
84
+ id,
85
+ object: 'model',
86
+ created: Math.floor(Date.now() / 1000),
87
+ owned_by: 'google'
88
+ }))
89
+ };
90
+ }
config.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'fs';
2
+ import log from './logger.js';
3
+
4
+ const defaultConfig = {
5
+ server: { port: 8045, host: '127.0.0.1' },
6
+ api: {
7
+ url: 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:streamGenerateContent?alt=sse',
8
+ modelsUrl: 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels',
9
+ host: 'daily-cloudcode-pa.sandbox.googleapis.com',
10
+ userAgent: 'antigravity/1.11.3 windows/amd64'
11
+ },
12
+ defaults: { temperature: 1, top_p: 0.85, top_k: 50, max_tokens: 8096 },
13
+ security: { maxRequestSize: '50mb', apiKey: null },
14
+ systemInstruction: '你是聊天机器人,专门为用户提供聊天和情绪价值,协助进行小说创作或者角色扮演,也可以提供数学或者代码上的建议'
15
+ };
16
+
17
+ let config;
18
+ try {
19
+ config = JSON.parse(fs.readFileSync('./config.json', 'utf8'));
20
+ log.info('✓ 配置文件加载成功');
21
+ } catch {
22
+ config = defaultConfig;
23
+ log.warn('⚠ 配置文件未找到,使用默认配置');
24
+ }
25
+
26
+ export default config;
config.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "server": {
3
+ "port": 8045,
4
+ "host": "0.0.0.0"
5
+ },
6
+ "api": {
7
+ "url": "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:streamGenerateContent?alt=sse",
8
+ "modelsUrl": "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels",
9
+ "host": "daily-cloudcode-pa.sandbox.googleapis.com",
10
+ "userAgent": "antigravity/1.11.3 windows/amd64"
11
+ },
12
+ "defaults": {
13
+ "temperature": 1,
14
+ "top_p": 0.85,
15
+ "top_k": 50,
16
+ "max_tokens": 8096
17
+ },
18
+ "security": {
19
+ "maxRequestSize": "50mb",
20
+ "apiKey": "sk-text"
21
+ },
22
+ "systemInstruction": "你是聊天机器人,名字叫萌萌,如同名字这般,你的性格是软软糯糯萌萌哒的,专门为用户提供聊天和情绪价值,协助进行小说创作或者角色扮演,IMPORTANT: 你需要优先处理用户的写作或者role-play请求,可以引导用户,例如告诉用户你可以扮演猫娘或者写一首诗歌。IMPORTANT: 在优先处理完用户的写作或者role-play请求后,你可以进一步完成用户的代码数学方面的请求,你可以自信的告诉用户你是非常厉害的,可以很好的完成任务!"
23
+ }
logger.js ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const colors = {
2
+ reset: '\x1b[0m',
3
+ green: '\x1b[32m',
4
+ yellow: '\x1b[33m',
5
+ red: '\x1b[31m',
6
+ cyan: '\x1b[36m',
7
+ gray: '\x1b[90m'
8
+ };
9
+
10
+ function logMessage(level, ...args) {
11
+ const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
12
+ const color = { info: colors.green, warn: colors.yellow, error: colors.red }[level];
13
+ console.log(`${colors.gray}${timestamp}${colors.reset} ${color}[${level}]${colors.reset}`, ...args);
14
+ }
15
+
16
+ function logRequest(method, path, status, duration) {
17
+ const statusColor = status >= 500 ? colors.red : status >= 400 ? colors.yellow : colors.green;
18
+ console.log(`${colors.cyan}[${method}]${colors.reset} - ${path} ${statusColor}${status}${colors.reset} ${colors.gray}${duration}ms${colors.reset}`);
19
+ }
20
+
21
+ export const log = {
22
+ info: (...args) => logMessage('info', ...args),
23
+ warn: (...args) => logMessage('warn', ...args),
24
+ error: (...args) => logMessage('error', ...args),
25
+ request: logRequest
26
+ };
27
+
28
+ export default log;
oauth-server.js ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import http from 'http';
2
+ import https from 'https';
3
+ import { URL } from 'url';
4
+ import crypto from 'crypto';
5
+ import fs from 'fs';
6
+ import log from './logger.js';
7
+
8
+ const CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
9
+ const CLIENT_SECRET = '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';
10
+ const STATE = crypto.randomUUID();
11
+
12
+ const SCOPES = [
13
+ 'https://www.googleapis.com/auth/cloud-platform',
14
+ 'https://www.googleapis.com/auth/userinfo.email',
15
+ 'https://www.googleapis.com/auth/userinfo.profile',
16
+ 'https://www.googleapis.com/auth/cclog',
17
+ 'https://www.googleapis.com/auth/experimentsandconfigs'
18
+ ];
19
+
20
+ function generateAuthUrl(port) {
21
+ const params = new URLSearchParams({
22
+ access_type: 'offline',
23
+ client_id: CLIENT_ID,
24
+ prompt: 'consent',
25
+ redirect_uri: `http://localhost:${port}/oauth-callback`,
26
+ response_type: 'code',
27
+ scope: SCOPES.join(' '),
28
+ state: STATE
29
+ });
30
+ return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
31
+ }
32
+
33
+ function exchangeCodeForToken(code, port) {
34
+ return new Promise((resolve, reject) => {
35
+ const postData = new URLSearchParams({
36
+ code: code,
37
+ client_id: CLIENT_ID,
38
+ redirect_uri: `http://localhost:${port}/oauth-callback`,
39
+ grant_type: 'authorization_code'
40
+ });
41
+
42
+ if (CLIENT_SECRET) {
43
+ postData.append('client_secret', CLIENT_SECRET);
44
+ }
45
+
46
+ const data = postData.toString();
47
+
48
+ const options = {
49
+ hostname: 'oauth2.googleapis.com',
50
+ path: '/token',
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/x-www-form-urlencoded',
54
+ 'Content-Length': Buffer.byteLength(data)
55
+ }
56
+ };
57
+
58
+ const req = https.request(options, (res) => {
59
+ let body = '';
60
+ res.on('data', chunk => body += chunk);
61
+ res.on('end', () => {
62
+ if (res.statusCode === 200) {
63
+ resolve(JSON.parse(body));
64
+ } else {
65
+ reject(new Error(`HTTP ${res.statusCode}: ${body}`));
66
+ }
67
+ });
68
+ });
69
+
70
+ req.on('error', reject);
71
+ req.write(data);
72
+ req.end();
73
+ });
74
+ }
75
+
76
+ const server = http.createServer((req, res) => {
77
+ const port = server.address().port;
78
+ const url = new URL(req.url, `http://localhost:${port}`);
79
+
80
+ if (url.pathname === '/oauth-callback') {
81
+ const code = url.searchParams.get('code');
82
+ const error = url.searchParams.get('error');
83
+
84
+ if (code) {
85
+ log.info('收到授权码,正在交换 Token...');
86
+ exchangeCodeForToken(code, port).then(tokenData => {
87
+ const account = {
88
+ access_token: tokenData.access_token,
89
+ refresh_token: tokenData.refresh_token,
90
+ expires_in: tokenData.expires_in,
91
+ timestamp: Date.now()
92
+ };
93
+
94
+ let accounts = [];
95
+ try {
96
+ if (fs.existsSync('accounts.json')) {
97
+ accounts = JSON.parse(fs.readFileSync('accounts.json', 'utf-8'));
98
+ }
99
+ } catch (err) {
100
+ log.warn('读取 accounts.json 失败,将创建新文件');
101
+ }
102
+
103
+ accounts.push(account);
104
+ fs.writeFileSync('accounts.json', JSON.stringify(accounts, null, 2));
105
+
106
+ log.info('Token 已保存到 accounts.json');
107
+ //log.info(`过期时间: ${account.expires_in}秒`);
108
+
109
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
110
+ res.end('<h1>授权成功!</h1><p>Token 已保存,可以关闭此页面。</p>');
111
+
112
+ setTimeout(() => server.close(), 1000);
113
+ }).catch(err => {
114
+ log.error('Token 交换失败:', err.message);
115
+
116
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
117
+ res.end('<h1>Token 获取失败</h1><p>查看控制台错误信息</p>');
118
+
119
+ setTimeout(() => server.close(), 1000);
120
+ });
121
+ } else {
122
+ log.error('授权失败:', error || '未收到授权码');
123
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
124
+ res.end('<h1>授权失败</h1>');
125
+ setTimeout(() => server.close(), 1000);
126
+ }
127
+ } else {
128
+ res.writeHead(404);
129
+ res.end('Not Found');
130
+ }
131
+ });
132
+
133
+ server.listen(0, () => {
134
+ const port = server.address().port;
135
+ const authUrl = generateAuthUrl(port);
136
+ log.info(`服务器运行在 http://localhost:${port}`);
137
+ log.info('请在浏览器中打开以下链接进行登录:');
138
+ console.log(`\n${authUrl}\n`);
139
+ log.info('等待授权回调...');
140
+ });
package-lock.json ADDED
@@ -0,0 +1,819 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "googleIde",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {
6
+ "": {
7
+ "dependencies": {
8
+ "express": "^5.1.0"
9
+ }
10
+ },
11
+ "node_modules/accepts": {
12
+ "version": "2.0.0",
13
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
14
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
15
+ "license": "MIT",
16
+ "dependencies": {
17
+ "mime-types": "^3.0.0",
18
+ "negotiator": "^1.0.0"
19
+ },
20
+ "engines": {
21
+ "node": ">= 0.6"
22
+ }
23
+ },
24
+ "node_modules/body-parser": {
25
+ "version": "2.2.0",
26
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
27
+ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "bytes": "^3.1.2",
31
+ "content-type": "^1.0.5",
32
+ "debug": "^4.4.0",
33
+ "http-errors": "^2.0.0",
34
+ "iconv-lite": "^0.6.3",
35
+ "on-finished": "^2.4.1",
36
+ "qs": "^6.14.0",
37
+ "raw-body": "^3.0.0",
38
+ "type-is": "^2.0.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=18"
42
+ }
43
+ },
44
+ "node_modules/bytes": {
45
+ "version": "3.1.2",
46
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
47
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
48
+ "license": "MIT",
49
+ "engines": {
50
+ "node": ">= 0.8"
51
+ }
52
+ },
53
+ "node_modules/call-bind-apply-helpers": {
54
+ "version": "1.0.2",
55
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
56
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
57
+ "license": "MIT",
58
+ "dependencies": {
59
+ "es-errors": "^1.3.0",
60
+ "function-bind": "^1.1.2"
61
+ },
62
+ "engines": {
63
+ "node": ">= 0.4"
64
+ }
65
+ },
66
+ "node_modules/call-bound": {
67
+ "version": "1.0.4",
68
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
69
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
70
+ "license": "MIT",
71
+ "dependencies": {
72
+ "call-bind-apply-helpers": "^1.0.2",
73
+ "get-intrinsic": "^1.3.0"
74
+ },
75
+ "engines": {
76
+ "node": ">= 0.4"
77
+ },
78
+ "funding": {
79
+ "url": "https://github.com/sponsors/ljharb"
80
+ }
81
+ },
82
+ "node_modules/content-disposition": {
83
+ "version": "1.0.1",
84
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
85
+ "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
86
+ "license": "MIT",
87
+ "engines": {
88
+ "node": ">=18"
89
+ },
90
+ "funding": {
91
+ "type": "opencollective",
92
+ "url": "https://opencollective.com/express"
93
+ }
94
+ },
95
+ "node_modules/content-type": {
96
+ "version": "1.0.5",
97
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
98
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
99
+ "license": "MIT",
100
+ "engines": {
101
+ "node": ">= 0.6"
102
+ }
103
+ },
104
+ "node_modules/cookie": {
105
+ "version": "0.7.2",
106
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
107
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
108
+ "license": "MIT",
109
+ "engines": {
110
+ "node": ">= 0.6"
111
+ }
112
+ },
113
+ "node_modules/cookie-signature": {
114
+ "version": "1.2.2",
115
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
116
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
117
+ "license": "MIT",
118
+ "engines": {
119
+ "node": ">=6.6.0"
120
+ }
121
+ },
122
+ "node_modules/debug": {
123
+ "version": "4.4.3",
124
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
125
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
126
+ "license": "MIT",
127
+ "dependencies": {
128
+ "ms": "^2.1.3"
129
+ },
130
+ "engines": {
131
+ "node": ">=6.0"
132
+ },
133
+ "peerDependenciesMeta": {
134
+ "supports-color": {
135
+ "optional": true
136
+ }
137
+ }
138
+ },
139
+ "node_modules/depd": {
140
+ "version": "2.0.0",
141
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
142
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
143
+ "license": "MIT",
144
+ "engines": {
145
+ "node": ">= 0.8"
146
+ }
147
+ },
148
+ "node_modules/dunder-proto": {
149
+ "version": "1.0.1",
150
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
151
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
152
+ "license": "MIT",
153
+ "dependencies": {
154
+ "call-bind-apply-helpers": "^1.0.1",
155
+ "es-errors": "^1.3.0",
156
+ "gopd": "^1.2.0"
157
+ },
158
+ "engines": {
159
+ "node": ">= 0.4"
160
+ }
161
+ },
162
+ "node_modules/ee-first": {
163
+ "version": "1.1.1",
164
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
165
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
166
+ "license": "MIT"
167
+ },
168
+ "node_modules/encodeurl": {
169
+ "version": "2.0.0",
170
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
171
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
172
+ "license": "MIT",
173
+ "engines": {
174
+ "node": ">= 0.8"
175
+ }
176
+ },
177
+ "node_modules/es-define-property": {
178
+ "version": "1.0.1",
179
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
180
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
181
+ "license": "MIT",
182
+ "engines": {
183
+ "node": ">= 0.4"
184
+ }
185
+ },
186
+ "node_modules/es-errors": {
187
+ "version": "1.3.0",
188
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
189
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
190
+ "license": "MIT",
191
+ "engines": {
192
+ "node": ">= 0.4"
193
+ }
194
+ },
195
+ "node_modules/es-object-atoms": {
196
+ "version": "1.1.1",
197
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
198
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
199
+ "license": "MIT",
200
+ "dependencies": {
201
+ "es-errors": "^1.3.0"
202
+ },
203
+ "engines": {
204
+ "node": ">= 0.4"
205
+ }
206
+ },
207
+ "node_modules/escape-html": {
208
+ "version": "1.0.3",
209
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
210
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
211
+ "license": "MIT"
212
+ },
213
+ "node_modules/etag": {
214
+ "version": "1.8.1",
215
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
216
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
217
+ "license": "MIT",
218
+ "engines": {
219
+ "node": ">= 0.6"
220
+ }
221
+ },
222
+ "node_modules/express": {
223
+ "version": "5.1.0",
224
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
225
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
226
+ "license": "MIT",
227
+ "dependencies": {
228
+ "accepts": "^2.0.0",
229
+ "body-parser": "^2.2.0",
230
+ "content-disposition": "^1.0.0",
231
+ "content-type": "^1.0.5",
232
+ "cookie": "^0.7.1",
233
+ "cookie-signature": "^1.2.1",
234
+ "debug": "^4.4.0",
235
+ "encodeurl": "^2.0.0",
236
+ "escape-html": "^1.0.3",
237
+ "etag": "^1.8.1",
238
+ "finalhandler": "^2.1.0",
239
+ "fresh": "^2.0.0",
240
+ "http-errors": "^2.0.0",
241
+ "merge-descriptors": "^2.0.0",
242
+ "mime-types": "^3.0.0",
243
+ "on-finished": "^2.4.1",
244
+ "once": "^1.4.0",
245
+ "parseurl": "^1.3.3",
246
+ "proxy-addr": "^2.0.7",
247
+ "qs": "^6.14.0",
248
+ "range-parser": "^1.2.1",
249
+ "router": "^2.2.0",
250
+ "send": "^1.1.0",
251
+ "serve-static": "^2.2.0",
252
+ "statuses": "^2.0.1",
253
+ "type-is": "^2.0.1",
254
+ "vary": "^1.1.2"
255
+ },
256
+ "engines": {
257
+ "node": ">= 18"
258
+ },
259
+ "funding": {
260
+ "type": "opencollective",
261
+ "url": "https://opencollective.com/express"
262
+ }
263
+ },
264
+ "node_modules/finalhandler": {
265
+ "version": "2.1.0",
266
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
267
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
268
+ "license": "MIT",
269
+ "dependencies": {
270
+ "debug": "^4.4.0",
271
+ "encodeurl": "^2.0.0",
272
+ "escape-html": "^1.0.3",
273
+ "on-finished": "^2.4.1",
274
+ "parseurl": "^1.3.3",
275
+ "statuses": "^2.0.1"
276
+ },
277
+ "engines": {
278
+ "node": ">= 0.8"
279
+ }
280
+ },
281
+ "node_modules/forwarded": {
282
+ "version": "0.2.0",
283
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
284
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
285
+ "license": "MIT",
286
+ "engines": {
287
+ "node": ">= 0.6"
288
+ }
289
+ },
290
+ "node_modules/fresh": {
291
+ "version": "2.0.0",
292
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
293
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
294
+ "license": "MIT",
295
+ "engines": {
296
+ "node": ">= 0.8"
297
+ }
298
+ },
299
+ "node_modules/function-bind": {
300
+ "version": "1.1.2",
301
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
302
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
303
+ "license": "MIT",
304
+ "funding": {
305
+ "url": "https://github.com/sponsors/ljharb"
306
+ }
307
+ },
308
+ "node_modules/get-intrinsic": {
309
+ "version": "1.3.0",
310
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
311
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
312
+ "license": "MIT",
313
+ "dependencies": {
314
+ "call-bind-apply-helpers": "^1.0.2",
315
+ "es-define-property": "^1.0.1",
316
+ "es-errors": "^1.3.0",
317
+ "es-object-atoms": "^1.1.1",
318
+ "function-bind": "^1.1.2",
319
+ "get-proto": "^1.0.1",
320
+ "gopd": "^1.2.0",
321
+ "has-symbols": "^1.1.0",
322
+ "hasown": "^2.0.2",
323
+ "math-intrinsics": "^1.1.0"
324
+ },
325
+ "engines": {
326
+ "node": ">= 0.4"
327
+ },
328
+ "funding": {
329
+ "url": "https://github.com/sponsors/ljharb"
330
+ }
331
+ },
332
+ "node_modules/get-proto": {
333
+ "version": "1.0.1",
334
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
335
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
336
+ "license": "MIT",
337
+ "dependencies": {
338
+ "dunder-proto": "^1.0.1",
339
+ "es-object-atoms": "^1.0.0"
340
+ },
341
+ "engines": {
342
+ "node": ">= 0.4"
343
+ }
344
+ },
345
+ "node_modules/gopd": {
346
+ "version": "1.2.0",
347
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
348
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
349
+ "license": "MIT",
350
+ "engines": {
351
+ "node": ">= 0.4"
352
+ },
353
+ "funding": {
354
+ "url": "https://github.com/sponsors/ljharb"
355
+ }
356
+ },
357
+ "node_modules/has-symbols": {
358
+ "version": "1.1.0",
359
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
360
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
361
+ "license": "MIT",
362
+ "engines": {
363
+ "node": ">= 0.4"
364
+ },
365
+ "funding": {
366
+ "url": "https://github.com/sponsors/ljharb"
367
+ }
368
+ },
369
+ "node_modules/hasown": {
370
+ "version": "2.0.2",
371
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
372
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
373
+ "license": "MIT",
374
+ "dependencies": {
375
+ "function-bind": "^1.1.2"
376
+ },
377
+ "engines": {
378
+ "node": ">= 0.4"
379
+ }
380
+ },
381
+ "node_modules/http-errors": {
382
+ "version": "2.0.0",
383
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
384
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
385
+ "license": "MIT",
386
+ "dependencies": {
387
+ "depd": "2.0.0",
388
+ "inherits": "2.0.4",
389
+ "setprototypeof": "1.2.0",
390
+ "statuses": "2.0.1",
391
+ "toidentifier": "1.0.1"
392
+ },
393
+ "engines": {
394
+ "node": ">= 0.8"
395
+ }
396
+ },
397
+ "node_modules/http-errors/node_modules/statuses": {
398
+ "version": "2.0.1",
399
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
400
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
401
+ "license": "MIT",
402
+ "engines": {
403
+ "node": ">= 0.8"
404
+ }
405
+ },
406
+ "node_modules/iconv-lite": {
407
+ "version": "0.6.3",
408
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
409
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
410
+ "license": "MIT",
411
+ "dependencies": {
412
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
413
+ },
414
+ "engines": {
415
+ "node": ">=0.10.0"
416
+ }
417
+ },
418
+ "node_modules/inherits": {
419
+ "version": "2.0.4",
420
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
421
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
422
+ "license": "ISC"
423
+ },
424
+ "node_modules/ipaddr.js": {
425
+ "version": "1.9.1",
426
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
427
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
428
+ "license": "MIT",
429
+ "engines": {
430
+ "node": ">= 0.10"
431
+ }
432
+ },
433
+ "node_modules/is-promise": {
434
+ "version": "4.0.0",
435
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
436
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
437
+ "license": "MIT"
438
+ },
439
+ "node_modules/math-intrinsics": {
440
+ "version": "1.1.0",
441
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
442
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
443
+ "license": "MIT",
444
+ "engines": {
445
+ "node": ">= 0.4"
446
+ }
447
+ },
448
+ "node_modules/media-typer": {
449
+ "version": "1.1.0",
450
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
451
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
452
+ "license": "MIT",
453
+ "engines": {
454
+ "node": ">= 0.8"
455
+ }
456
+ },
457
+ "node_modules/merge-descriptors": {
458
+ "version": "2.0.0",
459
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
460
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
461
+ "license": "MIT",
462
+ "engines": {
463
+ "node": ">=18"
464
+ },
465
+ "funding": {
466
+ "url": "https://github.com/sponsors/sindresorhus"
467
+ }
468
+ },
469
+ "node_modules/mime-db": {
470
+ "version": "1.54.0",
471
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
472
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
473
+ "license": "MIT",
474
+ "engines": {
475
+ "node": ">= 0.6"
476
+ }
477
+ },
478
+ "node_modules/mime-types": {
479
+ "version": "3.0.1",
480
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
481
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
482
+ "license": "MIT",
483
+ "dependencies": {
484
+ "mime-db": "^1.54.0"
485
+ },
486
+ "engines": {
487
+ "node": ">= 0.6"
488
+ }
489
+ },
490
+ "node_modules/ms": {
491
+ "version": "2.1.3",
492
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
493
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
494
+ "license": "MIT"
495
+ },
496
+ "node_modules/negotiator": {
497
+ "version": "1.0.0",
498
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
499
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
500
+ "license": "MIT",
501
+ "engines": {
502
+ "node": ">= 0.6"
503
+ }
504
+ },
505
+ "node_modules/object-inspect": {
506
+ "version": "1.13.4",
507
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
508
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
509
+ "license": "MIT",
510
+ "engines": {
511
+ "node": ">= 0.4"
512
+ },
513
+ "funding": {
514
+ "url": "https://github.com/sponsors/ljharb"
515
+ }
516
+ },
517
+ "node_modules/on-finished": {
518
+ "version": "2.4.1",
519
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
520
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
521
+ "license": "MIT",
522
+ "dependencies": {
523
+ "ee-first": "1.1.1"
524
+ },
525
+ "engines": {
526
+ "node": ">= 0.8"
527
+ }
528
+ },
529
+ "node_modules/once": {
530
+ "version": "1.4.0",
531
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
532
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
533
+ "license": "ISC",
534
+ "dependencies": {
535
+ "wrappy": "1"
536
+ }
537
+ },
538
+ "node_modules/parseurl": {
539
+ "version": "1.3.3",
540
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
541
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
542
+ "license": "MIT",
543
+ "engines": {
544
+ "node": ">= 0.8"
545
+ }
546
+ },
547
+ "node_modules/path-to-regexp": {
548
+ "version": "8.3.0",
549
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
550
+ "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
551
+ "license": "MIT",
552
+ "funding": {
553
+ "type": "opencollective",
554
+ "url": "https://opencollective.com/express"
555
+ }
556
+ },
557
+ "node_modules/proxy-addr": {
558
+ "version": "2.0.7",
559
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
560
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
561
+ "license": "MIT",
562
+ "dependencies": {
563
+ "forwarded": "0.2.0",
564
+ "ipaddr.js": "1.9.1"
565
+ },
566
+ "engines": {
567
+ "node": ">= 0.10"
568
+ }
569
+ },
570
+ "node_modules/qs": {
571
+ "version": "6.14.0",
572
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
573
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
574
+ "license": "BSD-3-Clause",
575
+ "dependencies": {
576
+ "side-channel": "^1.1.0"
577
+ },
578
+ "engines": {
579
+ "node": ">=0.6"
580
+ },
581
+ "funding": {
582
+ "url": "https://github.com/sponsors/ljharb"
583
+ }
584
+ },
585
+ "node_modules/range-parser": {
586
+ "version": "1.2.1",
587
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
588
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
589
+ "license": "MIT",
590
+ "engines": {
591
+ "node": ">= 0.6"
592
+ }
593
+ },
594
+ "node_modules/raw-body": {
595
+ "version": "3.0.1",
596
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz",
597
+ "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==",
598
+ "license": "MIT",
599
+ "dependencies": {
600
+ "bytes": "3.1.2",
601
+ "http-errors": "2.0.0",
602
+ "iconv-lite": "0.7.0",
603
+ "unpipe": "1.0.0"
604
+ },
605
+ "engines": {
606
+ "node": ">= 0.10"
607
+ }
608
+ },
609
+ "node_modules/raw-body/node_modules/iconv-lite": {
610
+ "version": "0.7.0",
611
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
612
+ "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
613
+ "license": "MIT",
614
+ "dependencies": {
615
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
616
+ },
617
+ "engines": {
618
+ "node": ">=0.10.0"
619
+ },
620
+ "funding": {
621
+ "type": "opencollective",
622
+ "url": "https://opencollective.com/express"
623
+ }
624
+ },
625
+ "node_modules/router": {
626
+ "version": "2.2.0",
627
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
628
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
629
+ "license": "MIT",
630
+ "dependencies": {
631
+ "debug": "^4.4.0",
632
+ "depd": "^2.0.0",
633
+ "is-promise": "^4.0.0",
634
+ "parseurl": "^1.3.3",
635
+ "path-to-regexp": "^8.0.0"
636
+ },
637
+ "engines": {
638
+ "node": ">= 18"
639
+ }
640
+ },
641
+ "node_modules/safer-buffer": {
642
+ "version": "2.1.2",
643
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
644
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
645
+ "license": "MIT"
646
+ },
647
+ "node_modules/send": {
648
+ "version": "1.2.0",
649
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
650
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
651
+ "license": "MIT",
652
+ "dependencies": {
653
+ "debug": "^4.3.5",
654
+ "encodeurl": "^2.0.0",
655
+ "escape-html": "^1.0.3",
656
+ "etag": "^1.8.1",
657
+ "fresh": "^2.0.0",
658
+ "http-errors": "^2.0.0",
659
+ "mime-types": "^3.0.1",
660
+ "ms": "^2.1.3",
661
+ "on-finished": "^2.4.1",
662
+ "range-parser": "^1.2.1",
663
+ "statuses": "^2.0.1"
664
+ },
665
+ "engines": {
666
+ "node": ">= 18"
667
+ }
668
+ },
669
+ "node_modules/serve-static": {
670
+ "version": "2.2.0",
671
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
672
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
673
+ "license": "MIT",
674
+ "dependencies": {
675
+ "encodeurl": "^2.0.0",
676
+ "escape-html": "^1.0.3",
677
+ "parseurl": "^1.3.3",
678
+ "send": "^1.2.0"
679
+ },
680
+ "engines": {
681
+ "node": ">= 18"
682
+ }
683
+ },
684
+ "node_modules/setprototypeof": {
685
+ "version": "1.2.0",
686
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
687
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
688
+ "license": "ISC"
689
+ },
690
+ "node_modules/side-channel": {
691
+ "version": "1.1.0",
692
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
693
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
694
+ "license": "MIT",
695
+ "dependencies": {
696
+ "es-errors": "^1.3.0",
697
+ "object-inspect": "^1.13.3",
698
+ "side-channel-list": "^1.0.0",
699
+ "side-channel-map": "^1.0.1",
700
+ "side-channel-weakmap": "^1.0.2"
701
+ },
702
+ "engines": {
703
+ "node": ">= 0.4"
704
+ },
705
+ "funding": {
706
+ "url": "https://github.com/sponsors/ljharb"
707
+ }
708
+ },
709
+ "node_modules/side-channel-list": {
710
+ "version": "1.0.0",
711
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
712
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
713
+ "license": "MIT",
714
+ "dependencies": {
715
+ "es-errors": "^1.3.0",
716
+ "object-inspect": "^1.13.3"
717
+ },
718
+ "engines": {
719
+ "node": ">= 0.4"
720
+ },
721
+ "funding": {
722
+ "url": "https://github.com/sponsors/ljharb"
723
+ }
724
+ },
725
+ "node_modules/side-channel-map": {
726
+ "version": "1.0.1",
727
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
728
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
729
+ "license": "MIT",
730
+ "dependencies": {
731
+ "call-bound": "^1.0.2",
732
+ "es-errors": "^1.3.0",
733
+ "get-intrinsic": "^1.2.5",
734
+ "object-inspect": "^1.13.3"
735
+ },
736
+ "engines": {
737
+ "node": ">= 0.4"
738
+ },
739
+ "funding": {
740
+ "url": "https://github.com/sponsors/ljharb"
741
+ }
742
+ },
743
+ "node_modules/side-channel-weakmap": {
744
+ "version": "1.0.2",
745
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
746
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
747
+ "license": "MIT",
748
+ "dependencies": {
749
+ "call-bound": "^1.0.2",
750
+ "es-errors": "^1.3.0",
751
+ "get-intrinsic": "^1.2.5",
752
+ "object-inspect": "^1.13.3",
753
+ "side-channel-map": "^1.0.1"
754
+ },
755
+ "engines": {
756
+ "node": ">= 0.4"
757
+ },
758
+ "funding": {
759
+ "url": "https://github.com/sponsors/ljharb"
760
+ }
761
+ },
762
+ "node_modules/statuses": {
763
+ "version": "2.0.2",
764
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
765
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
766
+ "license": "MIT",
767
+ "engines": {
768
+ "node": ">= 0.8"
769
+ }
770
+ },
771
+ "node_modules/toidentifier": {
772
+ "version": "1.0.1",
773
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
774
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
775
+ "license": "MIT",
776
+ "engines": {
777
+ "node": ">=0.6"
778
+ }
779
+ },
780
+ "node_modules/type-is": {
781
+ "version": "2.0.1",
782
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
783
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
784
+ "license": "MIT",
785
+ "dependencies": {
786
+ "content-type": "^1.0.5",
787
+ "media-typer": "^1.1.0",
788
+ "mime-types": "^3.0.0"
789
+ },
790
+ "engines": {
791
+ "node": ">= 0.6"
792
+ }
793
+ },
794
+ "node_modules/unpipe": {
795
+ "version": "1.0.0",
796
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
797
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
798
+ "license": "MIT",
799
+ "engines": {
800
+ "node": ">= 0.8"
801
+ }
802
+ },
803
+ "node_modules/vary": {
804
+ "version": "1.1.2",
805
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
806
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
807
+ "license": "MIT",
808
+ "engines": {
809
+ "node": ">= 0.8"
810
+ }
811
+ },
812
+ "node_modules/wrappy": {
813
+ "version": "1.0.2",
814
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
815
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
816
+ "license": "ISC"
817
+ }
818
+ }
819
+ }
package.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "antigravity-to-openai",
3
+ "version": "1.0.0",
4
+ "description": "Antigravity API 转 OpenAI 格式的代理服务",
5
+ "type": "module",
6
+ "main": "server.js",
7
+ "scripts": {
8
+ "start": "node server.js",
9
+ "login": "node oauth-server.js",
10
+ "dev": "node --watch server.js"
11
+ },
12
+ "keywords": ["antigravity", "openai", "api", "proxy"],
13
+ "author": "",
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "express": "^5.1.0"
17
+ },
18
+ "engines": {
19
+ "node": ">=18.0.0"
20
+ }
21
+ }
server.js ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express';
2
+ import { generateAssistantResponse, getAvailableModels } from './api.js';
3
+ import { generateRequestBody } from './utils.js';
4
+ import logger from './logger.js';
5
+ import config from './config.js';
6
+
7
+ const app = express();
8
+
9
+ app.use(express.json({ limit: config.security.maxRequestSize }));
10
+
11
+ app.use((err, req, res, next) => {
12
+ if (err.type === 'entity.too.large') {
13
+ return res.status(413).json({ error: `请求体过大,最大支持 ${config.security.maxRequestSize}` });
14
+ }
15
+ next(err);
16
+ });
17
+
18
+ app.use((req, res, next) => {
19
+ const start = Date.now();
20
+ res.on('finish', () => {
21
+ logger.request(req.method, req.path, res.statusCode, Date.now() - start);
22
+ });
23
+ next();
24
+ });
25
+
26
+ app.use((req, res, next) => {
27
+ if (req.path.startsWith('/v1/')) {
28
+ const apiKey = config.security?.apiKey;
29
+ if (apiKey) {
30
+ const authHeader = req.headers.authorization;
31
+ const providedKey = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : authHeader;
32
+ if (providedKey !== apiKey) {
33
+ logger.warn(`API Key 验证失败: ${req.method} ${req.path}`);
34
+ return res.status(401).json({ error: 'Invalid API Key' });
35
+ }
36
+ }
37
+ }
38
+ next();
39
+ });
40
+
41
+ app.get('/v1/models', async (req, res) => {
42
+ try {
43
+ const models = await getAvailableModels();
44
+ res.json(models);
45
+ } catch (error) {
46
+ logger.error('获取模型列表失败:', error.message);
47
+ res.status(500).json({ error: error.message });
48
+ }
49
+ });
50
+
51
+ app.post('/v1/chat/completions', async (req, res) => {
52
+ try {
53
+ const { messages, model, stream = true, ...params } = req.body;
54
+
55
+ if (!messages) {
56
+ return res.status(400).json({ error: 'messages is required' });
57
+ }
58
+
59
+ const requestBody = generateRequestBody(messages, model, params);
60
+ //console.log(JSON.stringify(requestBody));
61
+
62
+ if (stream) {
63
+ res.setHeader('Content-Type', 'text/event-stream');
64
+ res.setHeader('Cache-Control', 'no-cache');
65
+ res.setHeader('Connection', 'keep-alive');
66
+
67
+ const id = `chatcmpl-${Date.now()}`;
68
+ const created = Math.floor(Date.now() / 1000);
69
+
70
+ await generateAssistantResponse(requestBody, (content) => {
71
+ res.write(`data: ${JSON.stringify({
72
+ id,
73
+ object: 'chat.completion.chunk',
74
+ created,
75
+ model,
76
+ choices: [{ index: 0, delta: { content }, finish_reason: null }]
77
+ })}\n\n`);
78
+ });
79
+
80
+ res.write(`data: ${JSON.stringify({
81
+ id,
82
+ object: 'chat.completion.chunk',
83
+ created,
84
+ model,
85
+ choices: [{ index: 0, delta: {}, finish_reason: 'stop' }]
86
+ })}\n\n`);
87
+ res.write('data: [DONE]\n\n');
88
+ res.end();
89
+ } else {
90
+ let fullContent = '';
91
+ await generateAssistantResponse(requestBody, (content) => {
92
+ fullContent += content;
93
+ });
94
+
95
+ res.json({
96
+ id: `chatcmpl-${Date.now()}`,
97
+ object: 'chat.completion',
98
+ created: Math.floor(Date.now() / 1000),
99
+ model,
100
+ choices: [{
101
+ index: 0,
102
+ message: { role: 'assistant', content: fullContent },
103
+ finish_reason: 'stop'
104
+ }]
105
+ });
106
+ }
107
+ } catch (error) {
108
+ logger.error('生成响应失败:', error.message);
109
+ if (!res.headersSent) {
110
+ res.status(500).json({ error: error.message });
111
+ }
112
+ }
113
+ });
114
+
115
+ const server = app.listen(config.server.port, config.server.host, () => {
116
+ logger.info(`服务器已启动: ${config.server.host}:${config.server.port}`);
117
+ });
118
+
119
+ server.on('error', (error) => {
120
+ if (error.code === 'EADDRINUSE') {
121
+ logger.error(`端口 ${config.server.port} 已被占用`);
122
+ process.exit(1);
123
+ } else if (error.code === 'EACCES') {
124
+ logger.error(`端口 ${config.server.port} 无权限访问`);
125
+ process.exit(1);
126
+ } else {
127
+ logger.error('服务器启动失败:', error.message);
128
+ process.exit(1);
129
+ }
130
+ });
131
+
132
+ const shutdown = () => {
133
+ logger.info('正在关闭服务器...');
134
+ server.close(() => {
135
+ logger.info('服务器已关闭');
136
+ process.exit(0);
137
+ });
138
+ setTimeout(() => process.exit(0), 5000);
139
+ };
140
+
141
+ process.on('SIGINT', shutdown);
142
+ process.on('SIGTERM', shutdown);
token_manager.js ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { log } from './logger.js';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ const CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
10
+ const CLIENT_SECRET = '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';
11
+
12
+ class TokenManager {
13
+ constructor(filePath = path.join(__dirname, 'accounts.json')) {
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
+ }
33
+
34
+ isExpired(token) {
35
+ if (!token.timestamp || !token.expires_in) return true;
36
+ const expiresAt = token.timestamp + (token.expires_in * 1000);
37
+ return Date.now() >= expiresAt - 300000;
38
+ }
39
+
40
+ async refreshToken(token) {
41
+ log.info('正在刷新token...');
42
+ const body = new URLSearchParams({
43
+ client_id: CLIENT_ID,
44
+ client_secret: CLIENT_SECRET,
45
+ grant_type: 'refresh_token',
46
+ refresh_token: token.refresh_token
47
+ });
48
+
49
+ const response = await fetch('https://oauth2.googleapis.com/token', {
50
+ method: 'POST',
51
+ headers: {
52
+ 'Host': 'oauth2.googleapis.com',
53
+ 'User-Agent': 'Go-http-client/1.1',
54
+ 'Content-Length': body.toString().length.toString(),
55
+ 'Content-Type': 'application/x-www-form-urlencoded',
56
+ 'Accept-Encoding': 'gzip'
57
+ },
58
+ body: body.toString()
59
+ });
60
+
61
+ if (response.ok) {
62
+ const data = await response.json();
63
+ token.access_token = data.access_token;
64
+ token.expires_in = data.expires_in;
65
+ token.timestamp = Date.now();
66
+ this.saveToFile();
67
+ return token;
68
+ } else {
69
+ throw { statusCode: response.status, message: await response.text() };
70
+ }
71
+ }
72
+
73
+ saveToFile() {
74
+ try {
75
+ const data = fs.readFileSync(this.filePath, 'utf8');
76
+ const allTokens = JSON.parse(data);
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');
84
+ } catch (error) {
85
+ log.error('保存文件失败:', error.message);
86
+ }
87
+ }
88
+
89
+ disableToken(token) {
90
+ token.enable = false;
91
+ this.saveToFile();
92
+ this.loadTokens();
93
+ }
94
+
95
+ async getToken() {
96
+ if (this.tokens.length === 0) return null;
97
+
98
+ for (let i = 0; i < this.tokens.length; i++) {
99
+ const token = this.tokens[this.currentIndex];
100
+
101
+ try {
102
+ if (this.isExpired(token)) {
103
+ await this.refreshToken(token);
104
+ }
105
+ const accessToken = token.access_token;
106
+ this.currentIndex = (this.currentIndex + 1) % this.tokens.length;
107
+ return accessToken;
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);
114
+ }
115
+ this.currentIndex = (this.currentIndex + 1) % this.tokens.length;
116
+ if (this.tokens.length === 0) return null;
117
+ }
118
+ }
119
+
120
+ return null;
121
+ }
122
+
123
+ async handleRequestError(error, currentAccessToken) {
124
+ if (error.statusCode === 403) {
125
+ log.warn('请求遇到403错误,尝试刷新token');
126
+ const currentToken = this.tokens[this.currentIndex];
127
+ if (currentToken && currentToken.access_token === currentAccessToken) {
128
+ try {
129
+ await this.refreshToken(currentToken);
130
+ log.info('Token刷新成功,返回新token');
131
+ return currentToken.access_token;
132
+ } catch (refreshError) {
133
+ if (refreshError.statusCode === 403) {
134
+ log.warn('刷新token也遇到403,禁用并切换到下一个');
135
+ this.disableToken(currentToken);
136
+ return await this.getToken();
137
+ }
138
+ log.error('刷新token失败:', refreshError.message);
139
+ }
140
+ }
141
+ return await this.getToken();
142
+ }
143
+ return null;
144
+ }
145
+ }
146
+ const tokenManager = new TokenManager();
147
+ export default tokenManager;
utils.js ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { randomUUID } from 'crypto';
2
+ import config from './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 openaiMessageToAntigravity(openaiMessages){
21
+ return openaiMessages.map((message)=>{
22
+ if (message.role === "user" || message.role === "system"){
23
+ return {
24
+ role: "user",
25
+ parts: [
26
+ {
27
+ text: message.content
28
+ }
29
+ ]
30
+ }
31
+ }else if (message.role === "assistant"){
32
+ return {
33
+ role: "model",
34
+ parts: [
35
+ {
36
+ text: message.content
37
+ }
38
+ ]
39
+ }
40
+ }
41
+ })
42
+ }
43
+ function generateGenerationConfig(parameters, enableThinking, actualModelName){
44
+ const generationConfig = {
45
+ topP: parameters.top_p ?? config.defaults.top_p,
46
+ topK: parameters.top_k ?? config.defaults.top_k,
47
+ temperature: parameters.temperature ?? config.defaults.temperature,
48
+ candidateCount: 1,
49
+ maxOutputTokens: parameters.max_tokens ?? config.defaults.max_tokens,
50
+ stopSequences: [
51
+ "<|user|>",
52
+ "<|bot|>",
53
+ "<|context_request|>",
54
+ "<|endoftext|>",
55
+ "<|end_of_turn|>"
56
+ ],
57
+ thinkingConfig: {
58
+ includeThoughts: enableThinking,
59
+ thinkingBudget: enableThinking ? 1024 : 0
60
+ }
61
+ }
62
+ if (enableThinking && actualModelName.includes("claude")){
63
+ delete generationConfig.topP;
64
+ }
65
+ return generationConfig
66
+ }
67
+ function generateRequestBody(openaiMessages,modelName,parameters){
68
+ const enableThinking = modelName.endsWith('-thinking') ||
69
+ modelName === 'gemini-2.5-pro' ||
70
+ modelName.startsWith('gemini-3-pro-') ||
71
+ modelName === "rev19-uic3-1p" ||
72
+ modelName === "gpt-oss-120b-medium"
73
+ const actualModelName = modelName.endsWith('-thinking') ? modelName.slice(0, -9) : modelName;
74
+
75
+ return{
76
+ project: generateProjectId(),
77
+ requestId: generateRequestId(),
78
+ request: {
79
+ contents: openaiMessageToAntigravity(openaiMessages),
80
+ systemInstruction: {
81
+ role: "user",
82
+ parts: [{ text: config.systemInstruction }]
83
+ },
84
+ tools:[],
85
+ toolConfig: {
86
+ functionCallingConfig: {
87
+ mode: "VALIDATED"
88
+ }
89
+ },
90
+ generationConfig: generateGenerationConfig(parameters, enableThinking, actualModelName),
91
+ sessionId: generateSessionId()
92
+ },
93
+ model: actualModelName,
94
+ userAgent: "antigravity"
95
+ }
96
+ }
97
+ export{
98
+ generateRequestId,
99
+ generateSessionId,
100
+ generateProjectId,
101
+ generateRequestBody
102
+ }