使用axios替换fetch,优化代码结构,添加超时和代理支持,改为从.env加载
Browse files- .env.example +28 -0
- README.md +47 -31
- config.json +0 -25
- package-lock.json +145 -0
- package.json +8 -1
- src/api/client.js +152 -138
- src/auth/token_manager.js +23 -17
- src/config/config.js +64 -19
- src/server/index.js +1 -1
.env.example
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 服务器配置
|
| 2 |
+
PORT=8045
|
| 3 |
+
HOST=0.0.0.0
|
| 4 |
+
|
| 5 |
+
# API 配置
|
| 6 |
+
API_URL=https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:streamGenerateContent?alt=sse
|
| 7 |
+
API_MODELS_URL=https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels
|
| 8 |
+
API_NO_STREAM_URL=https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent
|
| 9 |
+
API_HOST=daily-cloudcode-pa.sandbox.googleapis.com
|
| 10 |
+
API_USER_AGENT=antigravity/1.11.3 windows/amd64
|
| 11 |
+
|
| 12 |
+
# 默认参数
|
| 13 |
+
DEFAULT_TEMPERATURE=1
|
| 14 |
+
DEFAULT_TOP_P=0.85
|
| 15 |
+
DEFAULT_TOP_K=50
|
| 16 |
+
DEFAULT_MAX_TOKENS=8096
|
| 17 |
+
|
| 18 |
+
# 安全配置
|
| 19 |
+
MAX_REQUEST_SIZE=50mb
|
| 20 |
+
API_KEY=sk-text
|
| 21 |
+
|
| 22 |
+
# 其他配置
|
| 23 |
+
USE_NATIVE_AXIOS=false
|
| 24 |
+
TIMEOUT=180000
|
| 25 |
+
# PROXY=http://127.0.0.1:7897
|
| 26 |
+
|
| 27 |
+
# 系统提示词
|
| 28 |
+
SYSTEM_INSTRUCTION=你是聊天机器人,名字叫萌萌,如同名字这般,你的性格是软软糯糯萌萌哒的,专门为用户提供聊天和情绪价值,协助进行小说创作或者角色扮演
|
README.md
CHANGED
|
@@ -25,20 +25,20 @@
|
|
| 25 |
npm install
|
| 26 |
```
|
| 27 |
|
| 28 |
-
### 2.
|
| 29 |
|
| 30 |
-
|
| 31 |
|
| 32 |
-
```
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
```
|
| 43 |
|
| 44 |
### 3. 登录获取 Token
|
|
@@ -178,19 +178,24 @@ curl http://localhost:8045/v1/chat/completions \
|
|
| 178 |
|
| 179 |
## 配置说明
|
| 180 |
|
| 181 |
-
###
|
| 182 |
|
| 183 |
-
|
|
| 184 |
|--------|------|--------|
|
| 185 |
-
| `
|
| 186 |
-
| `
|
| 187 |
-
| `
|
| 188 |
-
| `
|
| 189 |
-
| `
|
| 190 |
-
| `
|
| 191 |
-
| `
|
| 192 |
-
| `
|
| 193 |
-
| `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
|
| 195 |
## 开发命令
|
| 196 |
|
|
@@ -212,28 +217,39 @@ npm run login
|
|
| 212 |
├── data/
|
| 213 |
│ └── accounts.json # Token 存储(自动生成)
|
| 214 |
├── scripts/
|
| 215 |
-
│
|
|
|
|
| 216 |
├── src/
|
| 217 |
│ ├── api/
|
| 218 |
│ │ └── client.js # API 调用逻辑
|
| 219 |
│ ├── auth/
|
| 220 |
│ │ └── token_manager.js # Token 管理
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
│ ├── config/
|
| 222 |
│ │ └── config.js # 配置加载
|
| 223 |
│ ├── server/
|
| 224 |
│ │ └── index.js # 主服务器
|
| 225 |
-
│
|
| 226 |
-
│
|
| 227 |
-
│
|
| 228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
└── package.json # 项目配置
|
| 230 |
```
|
| 231 |
|
| 232 |
## 注意事项
|
| 233 |
|
| 234 |
-
1.
|
| 235 |
-
2. `
|
| 236 |
-
3.
|
| 237 |
4. 支持多账号轮换,提高可用性
|
| 238 |
5. Token 会自动刷新,无需手动维护
|
| 239 |
|
|
|
|
| 25 |
npm install
|
| 26 |
```
|
| 27 |
|
| 28 |
+
### 2. 配置环境变量
|
| 29 |
|
| 30 |
+
复制 `.env.example` 为 `.env` 并编辑配置:
|
| 31 |
|
| 32 |
+
```bash
|
| 33 |
+
cp .env.example .env
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
编辑 `.env` 文件配置服务器和 API 参数:
|
| 37 |
+
|
| 38 |
+
```env
|
| 39 |
+
PORT=8045
|
| 40 |
+
HOST=0.0.0.0
|
| 41 |
+
API_KEY=sk-text
|
| 42 |
```
|
| 43 |
|
| 44 |
### 3. 登录获取 Token
|
|
|
|
| 178 |
|
| 179 |
## 配置说明
|
| 180 |
|
| 181 |
+
### 环境变量 (.env)
|
| 182 |
|
| 183 |
+
| 环境变量 | 说明 | 默认值 |
|
| 184 |
|--------|------|--------|
|
| 185 |
+
| `PORT` | 服务端口 | 8045 |
|
| 186 |
+
| `HOST` | 监听地址 | 127.0.0.1 |
|
| 187 |
+
| `API_KEY` | API 认证密钥 | - |
|
| 188 |
+
| `MAX_REQUEST_SIZE` | 最大请求体大小 | 50mb |
|
| 189 |
+
| `DEFAULT_TEMPERATURE` | 默认温度参数 | 1 |
|
| 190 |
+
| `DEFAULT_TOP_P` | 默认 top_p | 0.85 |
|
| 191 |
+
| `DEFAULT_TOP_K` | 默认 top_k | 50 |
|
| 192 |
+
| `DEFAULT_MAX_TOKENS` | 默认最大 token 数 | 8096 |
|
| 193 |
+
| `USE_NATIVE_FETCH` | 使用原生 axios | false |
|
| 194 |
+
| `TIMEOUT` | 请求超时时间(毫秒) | 30000 |
|
| 195 |
+
| `PROXY` | 代理地址 | - |
|
| 196 |
+
| `SYSTEM_INSTRUCTION` | 系统提示词 | - |
|
| 197 |
+
|
| 198 |
+
完整配置示例请参考 `.env.example` 文件。
|
| 199 |
|
| 200 |
## 开发命令
|
| 201 |
|
|
|
|
| 217 |
├── data/
|
| 218 |
│ └── accounts.json # Token 存储(自动生成)
|
| 219 |
├── scripts/
|
| 220 |
+
│ ├── oauth-server.js # OAuth 登录服务
|
| 221 |
+
│ └── refresh-tokens.js # Token 刷新脚本
|
| 222 |
├── src/
|
| 223 |
│ ├── api/
|
| 224 |
│ │ └── client.js # API 调用逻辑
|
| 225 |
│ ├── auth/
|
| 226 |
│ │ └── token_manager.js # Token 管理
|
| 227 |
+
│ ├── bin/
|
| 228 |
+
│ │ ├── antigravity_requester_android_arm64 # Android ARM64 TLS 请求器
|
| 229 |
+
│ │ ├── antigravity_requester_linux_amd64 # Linux AMD64 TLS 请求器
|
| 230 |
+
│ │ └── antigravity_requester_windows_amd64.exe # Windows AMD64 TLS 请求器
|
| 231 |
│ ├── config/
|
| 232 |
│ │ └── config.js # 配置加载
|
| 233 |
│ ├── server/
|
| 234 |
│ │ └── index.js # 主服务器
|
| 235 |
+
│ ├── utils/
|
| 236 |
+
│ │ ├── idGenerator.js # ID 生成器
|
| 237 |
+
│ │ ├── logger.js # 日志模块
|
| 238 |
+
│ │ └── utils.js # 工具函数
|
| 239 |
+
│ └── AntigravityRequester.js # TLS 指纹请求器封装
|
| 240 |
+
├── test/
|
| 241 |
+
│ ├── test-request.js # 请求测试
|
| 242 |
+
│ └── test-transform.js # 转换测试
|
| 243 |
+
├── .env # 环境变量配置
|
| 244 |
+
├── .env.example # 环境变量配置示例
|
| 245 |
└── package.json # 项目配置
|
| 246 |
```
|
| 247 |
|
| 248 |
## 注意事项
|
| 249 |
|
| 250 |
+
1. 首次使用需要复制 `.env.example` 为 `.env` 并配置
|
| 251 |
+
2. 运行 `npm run login` 获取 Token
|
| 252 |
+
3. `.env` 和 `data/accounts.json` 包含敏感信息,请勿泄露
|
| 253 |
4. 支持多账号轮换,提高可用性
|
| 254 |
5. Token 会自动刷新,无需手动维护
|
| 255 |
|
config.json
DELETED
|
@@ -1,25 +0,0 @@
|
|
| 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 |
-
"noStreamUrl": "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent",
|
| 10 |
-
"host": "daily-cloudcode-pa.sandbox.googleapis.com",
|
| 11 |
-
"userAgent": "antigravity/1.11.3 windows/amd64"
|
| 12 |
-
},
|
| 13 |
-
"defaults": {
|
| 14 |
-
"temperature": 1,
|
| 15 |
-
"top_p": 0.85,
|
| 16 |
-
"top_k": 50,
|
| 17 |
-
"max_tokens": 8096
|
| 18 |
-
},
|
| 19 |
-
"security": {
|
| 20 |
-
"maxRequestSize": "50mb",
|
| 21 |
-
"apiKey": "sk-text"
|
| 22 |
-
},
|
| 23 |
-
"useNativeFetch": false,
|
| 24 |
-
"systemInstruction": "你是聊天机器人,名字叫萌萌,如同名字这般,你的性格是软软糯糯萌萌哒的,专门为用户提供聊天和情绪价值,协助进行小说创作或者角色扮演"
|
| 25 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
package-lock.json
CHANGED
|
@@ -9,6 +9,8 @@
|
|
| 9 |
"version": "1.0.0",
|
| 10 |
"license": "MIT",
|
| 11 |
"dependencies": {
|
|
|
|
|
|
|
| 12 |
"express": "^5.1.0"
|
| 13 |
},
|
| 14 |
"engines": {
|
|
@@ -28,6 +30,23 @@
|
|
| 28 |
"node": ">= 0.6"
|
| 29 |
}
|
| 30 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
"node_modules/body-parser": {
|
| 32 |
"version": "2.2.0",
|
| 33 |
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
|
@@ -86,6 +105,18 @@
|
|
| 86 |
"url": "https://github.com/sponsors/ljharb"
|
| 87 |
}
|
| 88 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
"node_modules/content-disposition": {
|
| 90 |
"version": "1.0.1",
|
| 91 |
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
|
|
@@ -143,6 +174,15 @@
|
|
| 143 |
}
|
| 144 |
}
|
| 145 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
"node_modules/depd": {
|
| 147 |
"version": "2.0.0",
|
| 148 |
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
|
@@ -152,6 +192,18 @@
|
|
| 152 |
"node": ">= 0.8"
|
| 153 |
}
|
| 154 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
"node_modules/dunder-proto": {
|
| 156 |
"version": "1.0.1",
|
| 157 |
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
|
@@ -211,6 +263,21 @@
|
|
| 211 |
"node": ">= 0.4"
|
| 212 |
}
|
| 213 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
"node_modules/escape-html": {
|
| 215 |
"version": "1.0.3",
|
| 216 |
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
|
@@ -285,6 +352,63 @@
|
|
| 285 |
"node": ">= 0.8"
|
| 286 |
}
|
| 287 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
"node_modules/forwarded": {
|
| 289 |
"version": "0.2.0",
|
| 290 |
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
|
@@ -373,6 +497,21 @@
|
|
| 373 |
"url": "https://github.com/sponsors/ljharb"
|
| 374 |
}
|
| 375 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
"node_modules/hasown": {
|
| 377 |
"version": "2.0.2",
|
| 378 |
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
|
@@ -574,6 +713,12 @@
|
|
| 574 |
"node": ">= 0.10"
|
| 575 |
}
|
| 576 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 577 |
"node_modules/qs": {
|
| 578 |
"version": "6.14.0",
|
| 579 |
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
|
|
|
| 9 |
"version": "1.0.0",
|
| 10 |
"license": "MIT",
|
| 11 |
"dependencies": {
|
| 12 |
+
"axios": "^1.13.2",
|
| 13 |
+
"dotenv": "^17.2.3",
|
| 14 |
"express": "^5.1.0"
|
| 15 |
},
|
| 16 |
"engines": {
|
|
|
|
| 30 |
"node": ">= 0.6"
|
| 31 |
}
|
| 32 |
},
|
| 33 |
+
"node_modules/asynckit": {
|
| 34 |
+
"version": "0.4.0",
|
| 35 |
+
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
| 36 |
+
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
| 37 |
+
"license": "MIT"
|
| 38 |
+
},
|
| 39 |
+
"node_modules/axios": {
|
| 40 |
+
"version": "1.13.2",
|
| 41 |
+
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
| 42 |
+
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
| 43 |
+
"license": "MIT",
|
| 44 |
+
"dependencies": {
|
| 45 |
+
"follow-redirects": "^1.15.6",
|
| 46 |
+
"form-data": "^4.0.4",
|
| 47 |
+
"proxy-from-env": "^1.1.0"
|
| 48 |
+
}
|
| 49 |
+
},
|
| 50 |
"node_modules/body-parser": {
|
| 51 |
"version": "2.2.0",
|
| 52 |
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
|
|
|
| 105 |
"url": "https://github.com/sponsors/ljharb"
|
| 106 |
}
|
| 107 |
},
|
| 108 |
+
"node_modules/combined-stream": {
|
| 109 |
+
"version": "1.0.8",
|
| 110 |
+
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
| 111 |
+
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
| 112 |
+
"license": "MIT",
|
| 113 |
+
"dependencies": {
|
| 114 |
+
"delayed-stream": "~1.0.0"
|
| 115 |
+
},
|
| 116 |
+
"engines": {
|
| 117 |
+
"node": ">= 0.8"
|
| 118 |
+
}
|
| 119 |
+
},
|
| 120 |
"node_modules/content-disposition": {
|
| 121 |
"version": "1.0.1",
|
| 122 |
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
|
|
|
|
| 174 |
}
|
| 175 |
}
|
| 176 |
},
|
| 177 |
+
"node_modules/delayed-stream": {
|
| 178 |
+
"version": "1.0.0",
|
| 179 |
+
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
| 180 |
+
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
| 181 |
+
"license": "MIT",
|
| 182 |
+
"engines": {
|
| 183 |
+
"node": ">=0.4.0"
|
| 184 |
+
}
|
| 185 |
+
},
|
| 186 |
"node_modules/depd": {
|
| 187 |
"version": "2.0.0",
|
| 188 |
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
|
|
|
| 192 |
"node": ">= 0.8"
|
| 193 |
}
|
| 194 |
},
|
| 195 |
+
"node_modules/dotenv": {
|
| 196 |
+
"version": "17.2.3",
|
| 197 |
+
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
| 198 |
+
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
|
| 199 |
+
"license": "BSD-2-Clause",
|
| 200 |
+
"engines": {
|
| 201 |
+
"node": ">=12"
|
| 202 |
+
},
|
| 203 |
+
"funding": {
|
| 204 |
+
"url": "https://dotenvx.com"
|
| 205 |
+
}
|
| 206 |
+
},
|
| 207 |
"node_modules/dunder-proto": {
|
| 208 |
"version": "1.0.1",
|
| 209 |
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
|
|
|
| 263 |
"node": ">= 0.4"
|
| 264 |
}
|
| 265 |
},
|
| 266 |
+
"node_modules/es-set-tostringtag": {
|
| 267 |
+
"version": "2.1.0",
|
| 268 |
+
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
| 269 |
+
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
| 270 |
+
"license": "MIT",
|
| 271 |
+
"dependencies": {
|
| 272 |
+
"es-errors": "^1.3.0",
|
| 273 |
+
"get-intrinsic": "^1.2.6",
|
| 274 |
+
"has-tostringtag": "^1.0.2",
|
| 275 |
+
"hasown": "^2.0.2"
|
| 276 |
+
},
|
| 277 |
+
"engines": {
|
| 278 |
+
"node": ">= 0.4"
|
| 279 |
+
}
|
| 280 |
+
},
|
| 281 |
"node_modules/escape-html": {
|
| 282 |
"version": "1.0.3",
|
| 283 |
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
|
|
|
| 352 |
"node": ">= 0.8"
|
| 353 |
}
|
| 354 |
},
|
| 355 |
+
"node_modules/follow-redirects": {
|
| 356 |
+
"version": "1.15.11",
|
| 357 |
+
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
| 358 |
+
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
| 359 |
+
"funding": [
|
| 360 |
+
{
|
| 361 |
+
"type": "individual",
|
| 362 |
+
"url": "https://github.com/sponsors/RubenVerborgh"
|
| 363 |
+
}
|
| 364 |
+
],
|
| 365 |
+
"license": "MIT",
|
| 366 |
+
"engines": {
|
| 367 |
+
"node": ">=4.0"
|
| 368 |
+
},
|
| 369 |
+
"peerDependenciesMeta": {
|
| 370 |
+
"debug": {
|
| 371 |
+
"optional": true
|
| 372 |
+
}
|
| 373 |
+
}
|
| 374 |
+
},
|
| 375 |
+
"node_modules/form-data": {
|
| 376 |
+
"version": "4.0.5",
|
| 377 |
+
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
| 378 |
+
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
| 379 |
+
"license": "MIT",
|
| 380 |
+
"dependencies": {
|
| 381 |
+
"asynckit": "^0.4.0",
|
| 382 |
+
"combined-stream": "^1.0.8",
|
| 383 |
+
"es-set-tostringtag": "^2.1.0",
|
| 384 |
+
"hasown": "^2.0.2",
|
| 385 |
+
"mime-types": "^2.1.12"
|
| 386 |
+
},
|
| 387 |
+
"engines": {
|
| 388 |
+
"node": ">= 6"
|
| 389 |
+
}
|
| 390 |
+
},
|
| 391 |
+
"node_modules/form-data/node_modules/mime-db": {
|
| 392 |
+
"version": "1.52.0",
|
| 393 |
+
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
| 394 |
+
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
| 395 |
+
"license": "MIT",
|
| 396 |
+
"engines": {
|
| 397 |
+
"node": ">= 0.6"
|
| 398 |
+
}
|
| 399 |
+
},
|
| 400 |
+
"node_modules/form-data/node_modules/mime-types": {
|
| 401 |
+
"version": "2.1.35",
|
| 402 |
+
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
| 403 |
+
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
| 404 |
+
"license": "MIT",
|
| 405 |
+
"dependencies": {
|
| 406 |
+
"mime-db": "1.52.0"
|
| 407 |
+
},
|
| 408 |
+
"engines": {
|
| 409 |
+
"node": ">= 0.6"
|
| 410 |
+
}
|
| 411 |
+
},
|
| 412 |
"node_modules/forwarded": {
|
| 413 |
"version": "0.2.0",
|
| 414 |
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
|
|
|
| 497 |
"url": "https://github.com/sponsors/ljharb"
|
| 498 |
}
|
| 499 |
},
|
| 500 |
+
"node_modules/has-tostringtag": {
|
| 501 |
+
"version": "1.0.2",
|
| 502 |
+
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
| 503 |
+
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
| 504 |
+
"license": "MIT",
|
| 505 |
+
"dependencies": {
|
| 506 |
+
"has-symbols": "^1.0.3"
|
| 507 |
+
},
|
| 508 |
+
"engines": {
|
| 509 |
+
"node": ">= 0.4"
|
| 510 |
+
},
|
| 511 |
+
"funding": {
|
| 512 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 513 |
+
}
|
| 514 |
+
},
|
| 515 |
"node_modules/hasown": {
|
| 516 |
"version": "2.0.2",
|
| 517 |
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
|
|
|
| 713 |
"node": ">= 0.10"
|
| 714 |
}
|
| 715 |
},
|
| 716 |
+
"node_modules/proxy-from-env": {
|
| 717 |
+
"version": "1.1.0",
|
| 718 |
+
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
| 719 |
+
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
| 720 |
+
"license": "MIT"
|
| 721 |
+
},
|
| 722 |
"node_modules/qs": {
|
| 723 |
"version": "6.14.0",
|
| 724 |
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
package.json
CHANGED
|
@@ -10,10 +10,17 @@
|
|
| 10 |
"refresh": "node scripts/refresh-tokens.js",
|
| 11 |
"dev": "node --watch src/server/index.js"
|
| 12 |
},
|
| 13 |
-
"keywords": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
"author": "",
|
| 15 |
"license": "MIT",
|
| 16 |
"dependencies": {
|
|
|
|
|
|
|
| 17 |
"express": "^5.1.0"
|
| 18 |
},
|
| 19 |
"engines": {
|
|
|
|
| 10 |
"refresh": "node scripts/refresh-tokens.js",
|
| 11 |
"dev": "node --watch src/server/index.js"
|
| 12 |
},
|
| 13 |
+
"keywords": [
|
| 14 |
+
"antigravity",
|
| 15 |
+
"openai",
|
| 16 |
+
"api",
|
| 17 |
+
"proxy"
|
| 18 |
+
],
|
| 19 |
"author": "",
|
| 20 |
"license": "MIT",
|
| 21 |
"dependencies": {
|
| 22 |
+
"axios": "^1.13.2",
|
| 23 |
+
"dotenv": "^17.2.3",
|
| 24 |
"express": "^5.1.0"
|
| 25 |
},
|
| 26 |
"engines": {
|
src/api/client.js
CHANGED
|
@@ -1,23 +1,101 @@
|
|
|
|
|
| 1 |
import tokenManager from '../auth/token_manager.js';
|
| 2 |
import config from '../config/config.js';
|
| 3 |
import { generateToolCallId } from '../utils/idGenerator.js';
|
| 4 |
import AntigravityRequester from '../AntigravityRequester.js';
|
| 5 |
|
|
|
|
| 6 |
let requester = null;
|
| 7 |
-
let
|
| 8 |
|
| 9 |
-
if (config.
|
|
|
|
|
|
|
| 10 |
try {
|
| 11 |
requester = new AntigravityRequester();
|
| 12 |
} catch (error) {
|
| 13 |
-
console.warn('AntigravityRequester
|
| 14 |
-
|
| 15 |
}
|
| 16 |
-
} else {
|
| 17 |
-
useNativeFetch = true;
|
| 18 |
}
|
| 19 |
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
if (!line.startsWith('data: ')) return;
|
| 22 |
|
| 23 |
try {
|
|
@@ -27,30 +105,27 @@ function processStreamLine(line, state, callback) {
|
|
| 27 |
if (parts) {
|
| 28 |
for (const part of parts) {
|
| 29 |
if (part.thought === true) {
|
|
|
|
| 30 |
if (!state.thinkingStarted) {
|
| 31 |
callback({ type: 'thinking', content: '<think>\n' });
|
| 32 |
state.thinkingStarted = true;
|
| 33 |
}
|
| 34 |
callback({ type: 'thinking', content: part.text || '' });
|
| 35 |
} else if (part.text !== undefined) {
|
|
|
|
| 36 |
if (state.thinkingStarted) {
|
| 37 |
callback({ type: 'thinking', content: '\n</think>\n' });
|
| 38 |
state.thinkingStarted = false;
|
| 39 |
}
|
| 40 |
callback({ type: 'text', content: part.text });
|
| 41 |
} else if (part.functionCall) {
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
type: 'function',
|
| 45 |
-
function: {
|
| 46 |
-
name: part.functionCall.name,
|
| 47 |
-
arguments: JSON.stringify(part.functionCall.args)
|
| 48 |
-
}
|
| 49 |
-
});
|
| 50 |
}
|
| 51 |
}
|
| 52 |
}
|
| 53 |
|
|
|
|
| 54 |
if (data.response?.candidates?.[0]?.finishReason && state.toolCalls.length > 0) {
|
| 55 |
if (state.thinkingStarted) {
|
| 56 |
callback({ type: 'thinking', content: '\n</think>\n' });
|
|
@@ -60,63 +135,43 @@ function processStreamLine(line, state, callback) {
|
|
| 60 |
state.toolCalls = [];
|
| 61 |
}
|
| 62 |
} catch (e) {
|
| 63 |
-
//
|
| 64 |
}
|
| 65 |
}
|
| 66 |
|
|
|
|
|
|
|
| 67 |
export async function generateAssistantResponse(requestBody, callback) {
|
| 68 |
const token = await tokenManager.getToken();
|
|
|
|
| 69 |
|
| 70 |
-
|
| 71 |
-
throw new Error('没有可用的token,请运行 npm run login 获取token');
|
| 72 |
-
}
|
| 73 |
-
|
| 74 |
-
const headers = {
|
| 75 |
-
'Host': config.api.host,
|
| 76 |
-
'User-Agent': config.api.userAgent,
|
| 77 |
-
'Authorization': `Bearer ${token.access_token}`,
|
| 78 |
-
'Content-Type': 'application/json',
|
| 79 |
-
'Accept-Encoding': 'gzip'
|
| 80 |
-
};
|
| 81 |
-
|
| 82 |
const state = { thinkingStarted: false, toolCalls: [] };
|
| 83 |
-
let buffer = '';
|
| 84 |
|
| 85 |
const processChunk = (chunk) => {
|
| 86 |
buffer += chunk;
|
| 87 |
const lines = buffer.split('\n');
|
| 88 |
-
buffer = lines.pop();
|
| 89 |
-
lines.forEach(line =>
|
| 90 |
};
|
| 91 |
|
| 92 |
-
if (
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
body: JSON.stringify(requestBody)
|
| 97 |
-
});
|
| 98 |
-
|
| 99 |
-
if (!response.ok) {
|
| 100 |
-
const errorBody = await response.text();
|
| 101 |
if (response.status === 403) tokenManager.disableCurrentToken(token);
|
| 102 |
-
throw new Error(`API请求失败 (${response.status}): ${errorBody}`);
|
| 103 |
-
}
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
}
|
| 113 |
} else {
|
| 114 |
-
const streamResponse = requester.antigravity_fetchStream(config.api.url,
|
| 115 |
-
method: 'POST',
|
| 116 |
-
headers,
|
| 117 |
-
body: JSON.stringify(requestBody)
|
| 118 |
-
});
|
| 119 |
-
|
| 120 |
let errorBody = '';
|
| 121 |
let statusCode = null;
|
| 122 |
|
|
@@ -126,20 +181,8 @@ export async function generateAssistantResponse(requestBody, callback) {
|
|
| 126 |
statusCode = status;
|
| 127 |
if (status === 403) tokenManager.disableCurrentToken(token);
|
| 128 |
})
|
| 129 |
-
.onData((chunk) =>
|
| 130 |
-
|
| 131 |
-
errorBody += chunk;
|
| 132 |
-
} else {
|
| 133 |
-
processChunk(chunk);
|
| 134 |
-
}
|
| 135 |
-
})
|
| 136 |
-
.onEnd(() => {
|
| 137 |
-
if (statusCode !== 200) {
|
| 138 |
-
reject(new Error(`API请求失败 (${statusCode}): ${errorBody}`));
|
| 139 |
-
} else {
|
| 140 |
-
resolve();
|
| 141 |
-
}
|
| 142 |
-
})
|
| 143 |
.onError(reject);
|
| 144 |
});
|
| 145 |
}
|
|
@@ -147,75 +190,54 @@ export async function generateAssistantResponse(requestBody, callback) {
|
|
| 147 |
|
| 148 |
export async function getAvailableModels() {
|
| 149 |
const token = await tokenManager.getToken();
|
|
|
|
| 150 |
|
| 151 |
-
|
| 152 |
-
throw new Error('没有可用的token,请运行 npm run login 获取token');
|
| 153 |
-
}
|
| 154 |
-
|
| 155 |
-
const headers = {
|
| 156 |
-
'Host': config.api.host,
|
| 157 |
-
'User-Agent': config.api.userAgent,
|
| 158 |
-
'Authorization': `Bearer ${token.access_token}`,
|
| 159 |
-
'Content-Type': 'application/json',
|
| 160 |
-
'Accept-Encoding': 'gzip'
|
| 161 |
-
};
|
| 162 |
-
|
| 163 |
-
const fetchFn = useNativeFetch ? fetch : (url, opts) => requester.antigravity_fetch(url, opts);
|
| 164 |
-
const response = await fetchFn(config.api.modelsUrl, {
|
| 165 |
-
method: 'POST',
|
| 166 |
-
headers,
|
| 167 |
-
body: JSON.stringify({})
|
| 168 |
-
});
|
| 169 |
-
|
| 170 |
-
const data = await response.json();
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
}
|
| 182 |
|
| 183 |
-
|
| 184 |
-
|
| 185 |
export async function generateAssistantResponseNoStream(requestBody) {
|
| 186 |
const token = await tokenManager.getToken();
|
|
|
|
| 187 |
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
}
|
| 191 |
-
|
| 192 |
-
const headers = {
|
| 193 |
-
'Host': config.api.host,
|
| 194 |
-
'User-Agent': config.api.userAgent,
|
| 195 |
-
'Authorization': `Bearer ${token.access_token}`,
|
| 196 |
-
'Content-Type': 'application/json',
|
| 197 |
-
'Accept-Encoding': 'gzip'
|
| 198 |
-
};
|
| 199 |
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
}
|
| 212 |
-
|
|
|
|
| 213 |
}
|
| 214 |
-
|
| 215 |
-
const data = await response.json();
|
| 216 |
-
//console.log(JSON.stringify(data,null,2))
|
| 217 |
-
const parts = data.response?.candidates?.[0]?.content?.parts || [];
|
| 218 |
|
|
|
|
|
|
|
| 219 |
let content = '';
|
| 220 |
let thinkingContent = '';
|
| 221 |
const toolCalls = [];
|
|
@@ -226,17 +248,11 @@ export async function generateAssistantResponseNoStream(requestBody) {
|
|
| 226 |
} else if (part.text !== undefined) {
|
| 227 |
content += part.text;
|
| 228 |
} else if (part.functionCall) {
|
| 229 |
-
toolCalls.push(
|
| 230 |
-
id: part.functionCall.id || generateToolCallId(),
|
| 231 |
-
type: 'function',
|
| 232 |
-
function: {
|
| 233 |
-
name: part.functionCall.name,
|
| 234 |
-
arguments: JSON.stringify(part.functionCall.args)
|
| 235 |
-
}
|
| 236 |
-
});
|
| 237 |
}
|
| 238 |
}
|
| 239 |
|
|
|
|
| 240 |
if (thinkingContent) {
|
| 241 |
content = `<think>\n${thinkingContent}\n</think>\n${content}`;
|
| 242 |
}
|
|
@@ -245,7 +261,5 @@ export async function generateAssistantResponseNoStream(requestBody) {
|
|
| 245 |
}
|
| 246 |
|
| 247 |
export function closeRequester() {
|
| 248 |
-
if (requester)
|
| 249 |
-
requester.close();
|
| 250 |
-
}
|
| 251 |
}
|
|
|
|
| 1 |
+
import axios from 'axios';
|
| 2 |
import tokenManager from '../auth/token_manager.js';
|
| 3 |
import config from '../config/config.js';
|
| 4 |
import { generateToolCallId } from '../utils/idGenerator.js';
|
| 5 |
import AntigravityRequester from '../AntigravityRequester.js';
|
| 6 |
|
| 7 |
+
// 请求客户端:优先使用 AntigravityRequester,失败则降级到 axios
|
| 8 |
let requester = null;
|
| 9 |
+
let useAxios = false;
|
| 10 |
|
| 11 |
+
if (config.useNativeAxios === true) {
|
| 12 |
+
useAxios = true;
|
| 13 |
+
} else {
|
| 14 |
try {
|
| 15 |
requester = new AntigravityRequester();
|
| 16 |
} catch (error) {
|
| 17 |
+
console.warn('AntigravityRequester 初始化失败,降级使用 axios:', error.message);
|
| 18 |
+
useAxios = true;
|
| 19 |
}
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
+
// ==================== 辅助函数 ====================
|
| 23 |
+
|
| 24 |
+
function buildHeaders(token) {
|
| 25 |
+
return {
|
| 26 |
+
'Host': config.api.host,
|
| 27 |
+
'User-Agent': config.api.userAgent,
|
| 28 |
+
'Authorization': `Bearer ${token.access_token}`,
|
| 29 |
+
'Content-Type': 'application/json',
|
| 30 |
+
'Accept-Encoding': 'gzip'
|
| 31 |
+
};
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
function buildAxiosConfig(url, headers, body = null) {
|
| 35 |
+
const axiosConfig = {
|
| 36 |
+
method: 'POST',
|
| 37 |
+
url,
|
| 38 |
+
headers,
|
| 39 |
+
timeout: config.timeout,
|
| 40 |
+
proxy: config.proxy ? (() => {
|
| 41 |
+
const proxyUrl = new URL(config.proxy);
|
| 42 |
+
return { protocol: proxyUrl.protocol.replace(':', ''), host: proxyUrl.hostname, port: parseInt(proxyUrl.port) };
|
| 43 |
+
})() : false
|
| 44 |
+
};
|
| 45 |
+
if (body !== null) axiosConfig.data = body;
|
| 46 |
+
return axiosConfig;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
function buildRequesterConfig(headers, body = null) {
|
| 50 |
+
const reqConfig = {
|
| 51 |
+
method: 'POST',
|
| 52 |
+
headers,
|
| 53 |
+
timeout_ms: config.timeout,
|
| 54 |
+
proxy: config.proxy
|
| 55 |
+
};
|
| 56 |
+
if (body !== null) reqConfig.body = JSON.stringify(body);
|
| 57 |
+
return reqConfig;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
// 统一错误处理
|
| 61 |
+
async function handleApiError(error, token) {
|
| 62 |
+
const status = error.response?.status || error.status;
|
| 63 |
+
let errorBody = error.message;
|
| 64 |
+
|
| 65 |
+
if (error.response?.data?.readable) {
|
| 66 |
+
const chunks = [];
|
| 67 |
+
for await (const chunk of error.response.data) {
|
| 68 |
+
chunks.push(chunk);
|
| 69 |
+
}
|
| 70 |
+
errorBody = Buffer.concat(chunks).toString();
|
| 71 |
+
} else if (typeof error.response?.data === 'object') {
|
| 72 |
+
errorBody = JSON.stringify(error.response.data, null, 2);
|
| 73 |
+
} else if (error.response?.data) {
|
| 74 |
+
errorBody = error.response.data;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
if (status === 403) {
|
| 78 |
+
tokenManager.disableCurrentToken(token);
|
| 79 |
+
throw new Error(`该账号没有使用权限,已自动禁用。错误详情: ${errorBody}`);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
throw new Error(`API请求失败 (${status}): ${errorBody}`);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
// 转换 functionCall 为 OpenAI 格式
|
| 86 |
+
function convertToToolCall(functionCall) {
|
| 87 |
+
return {
|
| 88 |
+
id: functionCall.id || generateToolCallId(),
|
| 89 |
+
type: 'function',
|
| 90 |
+
function: {
|
| 91 |
+
name: functionCall.name,
|
| 92 |
+
arguments: JSON.stringify(functionCall.args)
|
| 93 |
+
}
|
| 94 |
+
};
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
// 解析并发送流式响应片段(会修改 state 并触发 callback)
|
| 98 |
+
function parseAndEmitStreamChunk(line, state, callback) {
|
| 99 |
if (!line.startsWith('data: ')) return;
|
| 100 |
|
| 101 |
try {
|
|
|
|
| 105 |
if (parts) {
|
| 106 |
for (const part of parts) {
|
| 107 |
if (part.thought === true) {
|
| 108 |
+
// 思维链内容
|
| 109 |
if (!state.thinkingStarted) {
|
| 110 |
callback({ type: 'thinking', content: '<think>\n' });
|
| 111 |
state.thinkingStarted = true;
|
| 112 |
}
|
| 113 |
callback({ type: 'thinking', content: part.text || '' });
|
| 114 |
} else if (part.text !== undefined) {
|
| 115 |
+
// 普通文本内容
|
| 116 |
if (state.thinkingStarted) {
|
| 117 |
callback({ type: 'thinking', content: '\n</think>\n' });
|
| 118 |
state.thinkingStarted = false;
|
| 119 |
}
|
| 120 |
callback({ type: 'text', content: part.text });
|
| 121 |
} else if (part.functionCall) {
|
| 122 |
+
// 工具调用
|
| 123 |
+
state.toolCalls.push(convertToToolCall(part.functionCall));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
}
|
| 125 |
}
|
| 126 |
}
|
| 127 |
|
| 128 |
+
// 响应结束时发送工具调用
|
| 129 |
if (data.response?.candidates?.[0]?.finishReason && state.toolCalls.length > 0) {
|
| 130 |
if (state.thinkingStarted) {
|
| 131 |
callback({ type: 'thinking', content: '\n</think>\n' });
|
|
|
|
| 135 |
state.toolCalls = [];
|
| 136 |
}
|
| 137 |
} catch (e) {
|
| 138 |
+
// 忽略 JSON 解析错误
|
| 139 |
}
|
| 140 |
}
|
| 141 |
|
| 142 |
+
// ==================== 导出函数 ====================
|
| 143 |
+
|
| 144 |
export async function generateAssistantResponse(requestBody, callback) {
|
| 145 |
const token = await tokenManager.getToken();
|
| 146 |
+
if (!token) throw new Error('没有可用的token,请运行 npm run login 获取token');
|
| 147 |
|
| 148 |
+
const headers = buildHeaders(token);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
const state = { thinkingStarted: false, toolCalls: [] };
|
| 150 |
+
let buffer = ''; // 缓冲区:处理跨 chunk 的不完整行
|
| 151 |
|
| 152 |
const processChunk = (chunk) => {
|
| 153 |
buffer += chunk;
|
| 154 |
const lines = buffer.split('\n');
|
| 155 |
+
buffer = lines.pop(); // 保留最后一行(可能不完整)
|
| 156 |
+
lines.forEach(line => parseAndEmitStreamChunk(line, state, callback));
|
| 157 |
};
|
| 158 |
|
| 159 |
+
if (useAxios) {
|
| 160 |
+
try {
|
| 161 |
+
const axiosConfig = { ...buildAxiosConfig(config.api.url, headers, requestBody), responseType: 'stream' };
|
| 162 |
+
const response = await axios(axiosConfig);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
if (response.status === 403) tokenManager.disableCurrentToken(token);
|
|
|
|
|
|
|
| 164 |
|
| 165 |
+
response.data.on('data', chunk => processChunk(chunk.toString()));
|
| 166 |
+
await new Promise((resolve, reject) => {
|
| 167 |
+
response.data.on('end', resolve);
|
| 168 |
+
response.data.on('error', reject);
|
| 169 |
+
});
|
| 170 |
+
} catch (error) {
|
| 171 |
+
await handleApiError(error, token);
|
| 172 |
}
|
| 173 |
} else {
|
| 174 |
+
const streamResponse = requester.antigravity_fetchStream(config.api.url, buildRequesterConfig(headers, requestBody));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
let errorBody = '';
|
| 176 |
let statusCode = null;
|
| 177 |
|
|
|
|
| 181 |
statusCode = status;
|
| 182 |
if (status === 403) tokenManager.disableCurrentToken(token);
|
| 183 |
})
|
| 184 |
+
.onData((chunk) => statusCode !== 200 ? errorBody += chunk : processChunk(chunk))
|
| 185 |
+
.onEnd(() => statusCode !== 200 ? reject(new Error(`API请求失败 (${statusCode}): ${errorBody}`)) : resolve())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
.onError(reject);
|
| 187 |
});
|
| 188 |
}
|
|
|
|
| 190 |
|
| 191 |
export async function getAvailableModels() {
|
| 192 |
const token = await tokenManager.getToken();
|
| 193 |
+
if (!token) throw new Error('没有可用的token,请运行 npm run login 获取token');
|
| 194 |
|
| 195 |
+
const headers = buildHeaders(token);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
|
| 197 |
+
try {
|
| 198 |
+
const data = useAxios
|
| 199 |
+
? (await axios(buildAxiosConfig(config.api.modelsUrl, headers, {}))).data
|
| 200 |
+
: await (await requester.antigravity_fetch(config.api.modelsUrl, buildRequesterConfig(headers, {}))).json();
|
| 201 |
+
|
| 202 |
+
return {
|
| 203 |
+
object: 'list',
|
| 204 |
+
data: Object.keys(data.models).map(id => ({
|
| 205 |
+
id,
|
| 206 |
+
object: 'model',
|
| 207 |
+
created: Math.floor(Date.now() / 1000),
|
| 208 |
+
owned_by: 'google'
|
| 209 |
+
}))
|
| 210 |
+
};
|
| 211 |
+
} catch (error) {
|
| 212 |
+
await handleApiError(error, token);
|
| 213 |
+
}
|
| 214 |
}
|
| 215 |
|
|
|
|
|
|
|
| 216 |
export async function generateAssistantResponseNoStream(requestBody) {
|
| 217 |
const token = await tokenManager.getToken();
|
| 218 |
+
if (!token) throw new Error('没有可用的token,请运行 npm run login 获取token');
|
| 219 |
|
| 220 |
+
const headers = buildHeaders(token);
|
| 221 |
+
let data;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
|
| 223 |
+
try {
|
| 224 |
+
if (useAxios) {
|
| 225 |
+
data = (await axios(buildAxiosConfig(config.api.noStreamUrl, headers, requestBody))).data;
|
| 226 |
+
} else {
|
| 227 |
+
const response = await requester.antigravity_fetch(config.api.noStreamUrl, buildRequesterConfig(headers, requestBody));
|
| 228 |
+
if (response.status !== 200) {
|
| 229 |
+
const errorBody = await response.text();
|
| 230 |
+
if (response.status === 403) tokenManager.disableCurrentToken(token);
|
| 231 |
+
throw new Error(response.status === 403 ? `该账号没有使用权限,已自动禁用。错误详情: ${errorBody}` : `API请求失败 (${response.status}): ${errorBody}`);
|
| 232 |
+
}
|
| 233 |
+
data = await response.json();
|
| 234 |
}
|
| 235 |
+
} catch (error) {
|
| 236 |
+
await handleApiError(error, token);
|
| 237 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
|
| 239 |
+
// 解析响应内容
|
| 240 |
+
const parts = data.response?.candidates?.[0]?.content?.parts || [];
|
| 241 |
let content = '';
|
| 242 |
let thinkingContent = '';
|
| 243 |
const toolCalls = [];
|
|
|
|
| 248 |
} else if (part.text !== undefined) {
|
| 249 |
content += part.text;
|
| 250 |
} else if (part.functionCall) {
|
| 251 |
+
toolCalls.push(convertToToolCall(part.functionCall));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
}
|
| 253 |
}
|
| 254 |
|
| 255 |
+
// 拼接思维链标签
|
| 256 |
if (thinkingContent) {
|
| 257 |
content = `<think>\n${thinkingContent}\n</think>\n${content}`;
|
| 258 |
}
|
|
|
|
| 261 |
}
|
| 262 |
|
| 263 |
export function closeRequester() {
|
| 264 |
+
if (requester) requester.close();
|
|
|
|
|
|
|
| 265 |
}
|
src/auth/token_manager.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
| 1 |
import fs from 'fs';
|
| 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);
|
|
@@ -64,27 +66,31 @@ class TokenManager {
|
|
| 64 |
refresh_token: token.refresh_token
|
| 65 |
});
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
token.access_token = data.access_token;
|
| 82 |
-
token.expires_in = data.expires_in;
|
| 83 |
token.timestamp = Date.now();
|
| 84 |
this.saveToFile();
|
| 85 |
return token;
|
| 86 |
-
}
|
| 87 |
-
throw { statusCode: response
|
| 88 |
}
|
| 89 |
}
|
| 90 |
|
|
|
|
| 1 |
import fs from 'fs';
|
| 2 |
import path from 'path';
|
| 3 |
import { fileURLToPath } from 'url';
|
| 4 |
+
import axios from 'axios';
|
| 5 |
import { log } from '../utils/logger.js';
|
| 6 |
import { generateProjectId, generateSessionId } from '../utils/idGenerator.js';
|
| 7 |
+
import config from '../config/config.js';
|
| 8 |
|
| 9 |
const __filename = fileURLToPath(import.meta.url);
|
| 10 |
const __dirname = path.dirname(__filename);
|
|
|
|
| 66 |
refresh_token: token.refresh_token
|
| 67 |
});
|
| 68 |
|
| 69 |
+
try {
|
| 70 |
+
const response = await axios({
|
| 71 |
+
method: 'POST',
|
| 72 |
+
url: 'https://oauth2.googleapis.com/token',
|
| 73 |
+
headers: {
|
| 74 |
+
'Host': 'oauth2.googleapis.com',
|
| 75 |
+
'User-Agent': 'Go-http-client/1.1',
|
| 76 |
+
'Content-Type': 'application/x-www-form-urlencoded',
|
| 77 |
+
'Accept-Encoding': 'gzip'
|
| 78 |
+
},
|
| 79 |
+
data: body.toString(),
|
| 80 |
+
timeout: config.timeout,
|
| 81 |
+
proxy: config.proxy ? (() => {
|
| 82 |
+
const proxyUrl = new URL(config.proxy);
|
| 83 |
+
return { protocol: proxyUrl.protocol.replace(':', ''), host: proxyUrl.hostname, port: parseInt(proxyUrl.port) };
|
| 84 |
+
})() : false
|
| 85 |
+
});
|
| 86 |
|
| 87 |
+
token.access_token = response.data.access_token;
|
| 88 |
+
token.expires_in = response.data.expires_in;
|
|
|
|
|
|
|
| 89 |
token.timestamp = Date.now();
|
| 90 |
this.saveToFile();
|
| 91 |
return token;
|
| 92 |
+
} catch (error) {
|
| 93 |
+
throw { statusCode: error.response?.status, message: error.response?.data || error.message };
|
| 94 |
}
|
| 95 |
}
|
| 96 |
|
src/config/config.js
CHANGED
|
@@ -1,28 +1,73 @@
|
|
|
|
|
| 1 |
import fs from 'fs';
|
| 2 |
import log from '../utils/logger.js';
|
| 3 |
|
| 4 |
-
const
|
| 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 |
-
noStreamUrl:
|
| 10 |
-
host: 'daily-cloudcode-pa.sandbox.googleapis.com',
|
| 11 |
-
userAgent: 'antigravity/1.11.3 windows/amd64'
|
| 12 |
},
|
| 13 |
-
defaults: {
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
};
|
| 18 |
|
| 19 |
-
|
| 20 |
-
try {
|
| 21 |
-
config = JSON.parse(fs.readFileSync('./config.json', 'utf8'));
|
| 22 |
-
log.info('✓ 配置文件加载成功');
|
| 23 |
-
} catch {
|
| 24 |
-
config = defaultConfig;
|
| 25 |
-
log.warn('⚠ 配置文件未找到,使用默认配置');
|
| 26 |
-
}
|
| 27 |
|
| 28 |
export default config;
|
|
|
|
| 1 |
+
import dotenv from 'dotenv';
|
| 2 |
import fs from 'fs';
|
| 3 |
import log from '../utils/logger.js';
|
| 4 |
|
| 5 |
+
const envPath = '.env';
|
| 6 |
+
const defaultEnv = `# 服务器配置
|
| 7 |
+
PORT=8045
|
| 8 |
+
HOST=0.0.0.0
|
| 9 |
+
|
| 10 |
+
# API 配置
|
| 11 |
+
API_URL=https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:streamGenerateContent?alt=sse
|
| 12 |
+
API_MODELS_URL=https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels
|
| 13 |
+
API_NO_STREAM_URL=https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent
|
| 14 |
+
API_HOST=daily-cloudcode-pa.sandbox.googleapis.com
|
| 15 |
+
API_USER_AGENT=antigravity/1.11.3 windows/amd64
|
| 16 |
+
|
| 17 |
+
# 默认参数
|
| 18 |
+
DEFAULT_TEMPERATURE=1
|
| 19 |
+
DEFAULT_TOP_P=0.85
|
| 20 |
+
DEFAULT_TOP_K=50
|
| 21 |
+
DEFAULT_MAX_TOKENS=8096
|
| 22 |
+
|
| 23 |
+
# 安全配置
|
| 24 |
+
MAX_REQUEST_SIZE=50mb
|
| 25 |
+
API_KEY=sk-text
|
| 26 |
+
|
| 27 |
+
# 其他配置
|
| 28 |
+
USE_NATIVE_AXIOS=false
|
| 29 |
+
TIMEOUT=30000
|
| 30 |
+
# PROXY=http://127.0.0.1:7897
|
| 31 |
+
|
| 32 |
+
# 系统提示词
|
| 33 |
+
SYSTEM_INSTRUCTION=你是聊天机器人,名字叫萌萌,如同名字这般,你的性格是软软糯糯萌萌哒的,专门为用户提供聊天和情绪价值,协助进行小说创作或者角色扮演
|
| 34 |
+
`;
|
| 35 |
+
|
| 36 |
+
if (!fs.existsSync(envPath)) {
|
| 37 |
+
fs.writeFileSync(envPath, defaultEnv, 'utf8');
|
| 38 |
+
log.info('✓ 已创建默认 .env 文件');
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
dotenv.config();
|
| 42 |
+
|
| 43 |
+
const config = {
|
| 44 |
+
server: {
|
| 45 |
+
port: parseInt(process.env.PORT) || 8045,
|
| 46 |
+
host: process.env.HOST || '127.0.0.1'
|
| 47 |
+
},
|
| 48 |
api: {
|
| 49 |
+
url: process.env.API_URL || 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:streamGenerateContent?alt=sse',
|
| 50 |
+
modelsUrl: process.env.API_MODELS_URL || 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels',
|
| 51 |
+
noStreamUrl: process.env.API_NO_STREAM_URL || 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent',
|
| 52 |
+
host: process.env.API_HOST || 'daily-cloudcode-pa.sandbox.googleapis.com',
|
| 53 |
+
userAgent: process.env.API_USER_AGENT || 'antigravity/1.11.3 windows/amd64'
|
| 54 |
},
|
| 55 |
+
defaults: {
|
| 56 |
+
temperature: parseFloat(process.env.DEFAULT_TEMPERATURE) || 1,
|
| 57 |
+
top_p: parseFloat(process.env.DEFAULT_TOP_P) || 0.85,
|
| 58 |
+
top_k: parseInt(process.env.DEFAULT_TOP_K) || 50,
|
| 59 |
+
max_tokens: parseInt(process.env.DEFAULT_MAX_TOKENS) || 8096
|
| 60 |
+
},
|
| 61 |
+
security: {
|
| 62 |
+
maxRequestSize: process.env.MAX_REQUEST_SIZE || '50mb',
|
| 63 |
+
apiKey: process.env.API_KEY || null
|
| 64 |
+
},
|
| 65 |
+
useNativeAxios: process.env.USE_NATIVE_AXIOS !== 'false',
|
| 66 |
+
timeout: parseInt(process.env.TIMEOUT) || 30000,
|
| 67 |
+
proxy: process.env.PROXY || null,
|
| 68 |
+
systemInstruction: process.env.SYSTEM_INSTRUCTION || '你是聊天机器人,名字叫萌萌,如同名字这般,你的性格是软软糯糯萌萌哒的,专门为用户提供聊天和情绪价值,协助进行小说创作或者角色扮演'
|
| 69 |
};
|
| 70 |
|
| 71 |
+
log.info('✓ 配置加载成功');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
export default config;
|
src/server/index.js
CHANGED
|
@@ -57,7 +57,7 @@ app.post('/v1/chat/completions', async (req, res) => {
|
|
| 57 |
}
|
| 58 |
|
| 59 |
const requestBody = await generateRequestBody(messages, model, params, tools);
|
| 60 |
-
//console.log(JSON.stringify(requestBody,null,2));
|
| 61 |
|
| 62 |
if (stream) {
|
| 63 |
res.setHeader('Content-Type', 'text/event-stream');
|
|
|
|
| 57 |
}
|
| 58 |
|
| 59 |
const requestBody = await generateRequestBody(messages, model, params, tools);
|
| 60 |
+
// console.log(JSON.stringify(requestBody,null,2));
|
| 61 |
|
| 62 |
if (stream) {
|
| 63 |
res.setHeader('Content-Type', 'text/event-stream');
|