ai-zero-token 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -58
- package/dist/api.js +0 -1
- package/dist/cli/commands/ask.js +131 -5
- package/dist/cli/commands/clear.js +0 -1
- package/dist/cli/commands/help.js +15 -10
- package/dist/cli/commands/login.js +0 -1
- package/dist/cli/commands/models.js +0 -1
- package/dist/cli/commands/serve.js +42 -4
- package/dist/cli/commands/start.js +10 -0
- package/dist/cli/commands/status.js +1 -1
- package/dist/cli/index.js +4 -1
- package/dist/cli/shared.js +57 -6
- package/dist/cli.js +0 -1
- package/dist/core/context.js +7 -2
- package/dist/core/models/openai-codex-models.js +0 -1
- package/dist/core/providers/http-client.js +97 -9
- package/dist/core/providers/openai-codex/chat.js +217 -24
- package/dist/core/providers/openai-codex/oauth.js +15 -4
- package/dist/core/providers/openai-codex/pkce.js +0 -1
- package/dist/core/services/auth-service.js +89 -16
- package/dist/core/services/chat-service.js +24 -14
- package/dist/core/services/config-service.js +0 -1
- package/dist/core/services/image-service.js +360 -0
- package/dist/core/services/model-service.js +4 -2
- package/dist/core/store/profile-store.js +79 -6
- package/dist/core/store/settings-store.js +1 -2
- package/dist/core/types.js +0 -1
- package/dist/http.js +0 -1
- package/dist/models.js +0 -1
- package/dist/oauth.js +0 -1
- package/dist/pkce.js +0 -1
- package/dist/server/admin-page.js +2615 -0
- package/dist/server/app.js +566 -39
- package/dist/server/index.js +13 -3
- package/dist/store.js +0 -1
- package/package.json +14 -6
- package/dist/api.js.map +0 -1
- package/dist/cli/commands/ask.js.map +0 -1
- package/dist/cli/commands/clear.js.map +0 -1
- package/dist/cli/commands/help.js.map +0 -1
- package/dist/cli/commands/login.js.map +0 -1
- package/dist/cli/commands/models.js.map +0 -1
- package/dist/cli/commands/serve.js.map +0 -1
- package/dist/cli/commands/status.js.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/shared.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/core/context.js.map +0 -1
- package/dist/core/models/openai-codex-models.js.map +0 -1
- package/dist/core/providers/http-client.js.map +0 -1
- package/dist/core/providers/openai-codex/chat.js.map +0 -1
- package/dist/core/providers/openai-codex/oauth.js.map +0 -1
- package/dist/core/providers/openai-codex/pkce.js.map +0 -1
- package/dist/core/services/auth-service.js.map +0 -1
- package/dist/core/services/chat-service.js.map +0 -1
- package/dist/core/services/config-service.js.map +0 -1
- package/dist/core/services/model-service.js.map +0 -1
- package/dist/core/store/profile-store.js.map +0 -1
- package/dist/core/store/settings-store.js.map +0 -1
- package/dist/core/types.js.map +0 -1
- package/dist/http.js.map +0 -1
- package/dist/models.js.map +0 -1
- package/dist/oauth.js.map +0 -1
- package/dist/pkce.js.map +0 -1
- package/dist/server/app.js.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/dist/store.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,12 +1,51 @@
|
|
|
1
1
|
# AI Zero Token
|
|
2
2
|
|
|
3
|
+
> 实验工具,不建议直接作为生产环境网关使用。
|
|
4
|
+
|
|
3
5
|
AI Zero Token 是一个本地优先的单用户 AI CLI 和本地网关。
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
它把账号授权能力整理成 OpenAI 风格接口,重点是把图片生成能力也代理出来:
|
|
8
|
+
|
|
9
|
+
- `POST /v1/images/generations`
|
|
10
|
+
- `POST /v1/responses`
|
|
11
|
+
- `POST /v1/chat/completions`
|
|
12
|
+
- `GET /v1/models`
|
|
13
|
+
|
|
14
|
+
很多用户已经有 ChatGPT 之类产品的账号、订阅或可用授权能力,但缺少一个统一、可脚本化、可本地集成的入口。AI Zero Token 要做的,就是把这类已有授权能力整理成一个可直接使用的命令行工具和本地接口。
|
|
15
|
+
|
|
16
|
+
## 这次迭代亮点
|
|
17
|
+
|
|
18
|
+
- 直接代理 `gpt-image-2`,把图片生成能力暴露成 OpenAI 风格 `images.generations` 接口
|
|
19
|
+
- 启动 `azt start` 后即可获得本地管理页和本地网关,适合脚本、前端和自动化流程接入
|
|
20
|
+
- 支持多账号保存、切换当前账号、查看账号套餐 plan,以及当前账号是否支持生图
|
|
21
|
+
- `free` 账号会在管理页直接预警,并在网关层明确拦截生图请求
|
|
22
|
+
|
|
23
|
+
如果你只关心一句话,可以把这个项目理解为:
|
|
24
|
+
|
|
25
|
+
> 一个把账号授权能力代理成本地 OpenAI 风格接口的实验工具,这次重点补齐了 `gpt-image-2` 生图接口。
|
|
26
|
+
|
|
27
|
+
## 一条命令开始
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install -g ai-zero-token
|
|
31
|
+
azt start
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
启动后会打开本地管理页,并暴露 OpenAI 风格接口:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
http://127.0.0.1:8787/v1
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 界面预览
|
|
41
|
+
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
## 生图结果预览
|
|
6
45
|
|
|
7
|
-
|
|
46
|
+
下图通过 AI Zero Token 本地网关调用 `POST /v1/images/generations` 生成,模型为 `gpt-image-2`。
|
|
8
47
|
|
|
9
|
-
|
|
48
|
+

|
|
10
49
|
|
|
11
50
|
## 为什么做这个项目
|
|
12
51
|
|
|
@@ -19,21 +58,36 @@ AI Zero Token 是一个本地优先的单用户 AI CLI 和本地网关。
|
|
|
19
58
|
|
|
20
59
|
AI Zero Token 就是围绕这些问题设计的。
|
|
21
60
|
|
|
61
|
+
## 它能解决什么
|
|
62
|
+
|
|
63
|
+
- 想把 ChatGPT 的图片生成能力接到自己的前端、小工具或脚本里
|
|
64
|
+
- 想先用本地网关验证产品原型,而不是一上来就重做整套后端
|
|
65
|
+
- 想统一成 OpenAI 风格接口,减少接入成本
|
|
66
|
+
- 想研究一条完整的 OAuth -> token -> 本地状态 -> 网关 -> 上游请求链路
|
|
67
|
+
|
|
68
|
+
## 架构概览
|
|
69
|
+
|
|
70
|
+

|
|
71
|
+
|
|
22
72
|
## 当前能做什么
|
|
23
73
|
|
|
74
|
+
- 通过 `POST /v1/images/generations` 代理 `gpt-image-2` 生图请求
|
|
24
75
|
- 通过 OpenAI Codex OAuth 登录
|
|
25
76
|
- 在本地保存 `access_token` 和 `refresh_token`
|
|
77
|
+
- 支持保存多个账号 profile,并手动切换当前生效账号
|
|
26
78
|
- 在 token 过期时自动刷新
|
|
27
|
-
- 通过
|
|
28
|
-
-
|
|
79
|
+
- 通过 `azt start` 一键启动本地 HTTP 网关和管理页面
|
|
80
|
+
- 在管理页面里完成多账号登录、查看账号状态、切换当前账号、切换默认模型、测试接口
|
|
29
81
|
- 暴露 OpenAI 风格接口:
|
|
30
82
|
- `GET /v1/models`
|
|
31
83
|
- `POST /v1/responses`
|
|
32
|
-
|
|
84
|
+
- `POST /v1/chat/completions`
|
|
85
|
+
- `POST /v1/images/generations`
|
|
33
86
|
|
|
34
87
|
## 适合谁用
|
|
35
88
|
|
|
36
89
|
- 想把账号授权能力包装成本地工具的人
|
|
90
|
+
- 想把 `gpt-image-2` 生图接口直接接给脚本、前端或自动化流程的人
|
|
37
91
|
- 想做自己的 AI 网关、AI CLI、AI 桌面端的人
|
|
38
92
|
- 想学习一条完整 OAuth -> token -> CLI -> HTTP API 链路的人
|
|
39
93
|
- 想把 AI 能力接入脚本、前端或自动化流程的人
|
|
@@ -62,7 +116,7 @@ npm install
|
|
|
62
116
|
直接运行源码:
|
|
63
117
|
|
|
64
118
|
```bash
|
|
65
|
-
bun src/cli.ts
|
|
119
|
+
bun src/cli.ts start
|
|
66
120
|
```
|
|
67
121
|
|
|
68
122
|
### 从 npm 安装 CLI
|
|
@@ -76,7 +130,7 @@ npm install -g ai-zero-token
|
|
|
76
130
|
安装后验证:
|
|
77
131
|
|
|
78
132
|
```bash
|
|
79
|
-
azt
|
|
133
|
+
azt start
|
|
80
134
|
```
|
|
81
135
|
|
|
82
136
|
如果你是为了开发、构建、`npm link`、`npm pack` 或准备发布,单独看:
|
|
@@ -85,45 +139,53 @@ azt help
|
|
|
85
139
|
|
|
86
140
|
## 快速开始
|
|
87
141
|
|
|
88
|
-
|
|
142
|
+
启动本地网关和管理页面:
|
|
143
|
+
|
|
89
144
|
```bash
|
|
90
|
-
azt
|
|
145
|
+
azt start
|
|
91
146
|
```
|
|
92
147
|
|
|
93
|
-
|
|
148
|
+
执行后会自动打开管理页,默认地址:
|
|
94
149
|
|
|
95
|
-
```
|
|
96
|
-
|
|
150
|
+
```text
|
|
151
|
+
http://127.0.0.1:8787
|
|
97
152
|
```
|
|
98
153
|
|
|
99
|
-
|
|
154
|
+
默认会监听:
|
|
100
155
|
|
|
101
|
-
```
|
|
102
|
-
|
|
156
|
+
```text
|
|
157
|
+
0.0.0.0:8787
|
|
103
158
|
```
|
|
104
159
|
|
|
105
|
-
|
|
160
|
+
这表示本机可以用 `127.0.0.1:8787` 访问,局域网内其他设备也可以用你的机器 IP 访问,比如 `http://172.26.66.132:8787`。
|
|
106
161
|
|
|
107
|
-
|
|
108
|
-
azt ask "请只回复 OK"
|
|
109
|
-
```
|
|
162
|
+
接下来在管理页里完成这几件事:
|
|
110
163
|
|
|
111
|
-
|
|
164
|
+
- 登录一个或多个 OpenAI Codex 账号
|
|
165
|
+
- 查看当前账号状态和过期时间
|
|
166
|
+
- 在已保存账号之间切换当前使用账号
|
|
167
|
+
- 切换默认模型
|
|
168
|
+
- 直接测试 `models`、`responses`、`chat.completions`
|
|
169
|
+
- 直接测试 `images.generations` 生图接口
|
|
112
170
|
|
|
113
|
-
|
|
114
|
-
|
|
171
|
+
如果你要把它接到自己的客户端,只需要把 Base URL 指向:
|
|
172
|
+
|
|
173
|
+
```text
|
|
174
|
+
http://127.0.0.1:8787/v1
|
|
115
175
|
```
|
|
116
176
|
|
|
117
|
-
|
|
177
|
+
如果你要让本地网页直接从浏览器请求这个网关,现在已经默认开启 CORS。
|
|
178
|
+
|
|
179
|
+
如需限制来源,可以在启动前指定:
|
|
118
180
|
|
|
119
181
|
```bash
|
|
120
|
-
azt
|
|
182
|
+
AZT_CORS_ORIGIN=http://127.0.0.1:8124 azt start
|
|
121
183
|
```
|
|
122
184
|
|
|
123
|
-
|
|
185
|
+
多个来源可用英文逗号分隔:
|
|
124
186
|
|
|
125
187
|
```bash
|
|
126
|
-
azt
|
|
188
|
+
AZT_CORS_ORIGIN=http://127.0.0.1:8124,http://localhost:3000 azt start
|
|
127
189
|
```
|
|
128
190
|
|
|
129
191
|
如果你当前还没有全局命令,也可以把上面的 `azt` 临时替换成:
|
|
@@ -132,67 +194,68 @@ azt clear
|
|
|
132
194
|
bun src/cli.ts
|
|
133
195
|
```
|
|
134
196
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
```bash
|
|
138
|
-
bun src/cli.ts login
|
|
139
|
-
```
|
|
197
|
+
例如:`bun src/cli.ts start`
|
|
140
198
|
|
|
141
199
|
## 网关使用说明
|
|
142
200
|
|
|
143
|
-
如果你主要把 AI Zero Token
|
|
201
|
+
如果你主要把 AI Zero Token 当作本地网关来使用,推荐只记住一个命令:
|
|
144
202
|
|
|
145
|
-
### 1.
|
|
203
|
+
### 1. 启动
|
|
146
204
|
|
|
147
205
|
```bash
|
|
148
|
-
azt
|
|
206
|
+
azt start
|
|
149
207
|
```
|
|
150
208
|
|
|
151
|
-
|
|
209
|
+
启动后会自动打开管理页。管理页就是默认工作入口,你可以在里面直接:
|
|
152
210
|
|
|
153
|
-
|
|
211
|
+
- 触发 OpenAI Codex OAuth 登录并新增账号
|
|
212
|
+
- 查看当前账号、已保存账号列表、过期时间、token 摘要
|
|
213
|
+
- 查看账号套餐 plan 和当前账号是否支持生图
|
|
214
|
+
- 在多个已保存账号之间切换当前使用账号
|
|
215
|
+
- 删除单个本地账号,或一键清空全部本地账号
|
|
216
|
+
- 切换默认模型
|
|
217
|
+
- 测试 `models` / `responses` / `chat.completions`
|
|
218
|
+
- 测试 `images.generations`
|
|
154
219
|
|
|
155
|
-
|
|
156
|
-
azt serve
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
CLI 一旦执行 `serve`,就会进入本地网关模式。
|
|
220
|
+
管理页里邮箱默认脱敏显示,需要手动点击“查看邮箱”才会显示明文。
|
|
160
221
|
|
|
161
222
|
默认监听地址:
|
|
162
223
|
|
|
163
224
|
```text
|
|
164
|
-
http://
|
|
225
|
+
http://0.0.0.0:8787
|
|
165
226
|
```
|
|
166
227
|
|
|
167
|
-
|
|
228
|
+
本机浏览器访问:
|
|
168
229
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
```bash
|
|
172
|
-
curl http://127.0.0.1:8787/_gateway/health
|
|
230
|
+
```text
|
|
231
|
+
http://127.0.0.1:8787
|
|
173
232
|
```
|
|
174
233
|
|
|
175
|
-
|
|
234
|
+
默认 CORS 来源:
|
|
176
235
|
|
|
177
|
-
```
|
|
178
|
-
|
|
236
|
+
```text
|
|
237
|
+
*
|
|
179
238
|
```
|
|
180
239
|
|
|
181
|
-
###
|
|
240
|
+
### 2. 把它接到你的客户端
|
|
182
241
|
|
|
183
|
-
|
|
242
|
+
客户端或 SDK 里把 Base URL 改成:
|
|
184
243
|
|
|
185
|
-
```
|
|
186
|
-
|
|
244
|
+
```text
|
|
245
|
+
http://127.0.0.1:8787/v1
|
|
187
246
|
```
|
|
188
247
|
|
|
248
|
+
如果客户端必须填写 API Key,可以填任意非空占位值;真正起作用的是本地网关地址。
|
|
249
|
+
|
|
250
|
+
### 3. 查看模型列表
|
|
251
|
+
|
|
189
252
|
OpenAI 风格模型接口:
|
|
190
253
|
|
|
191
254
|
```bash
|
|
192
255
|
curl http://127.0.0.1:8787/v1/models
|
|
193
256
|
```
|
|
194
257
|
|
|
195
|
-
###
|
|
258
|
+
### 4. 调用对话接口
|
|
196
259
|
|
|
197
260
|
最小请求示例:
|
|
198
261
|
|
|
@@ -210,13 +273,63 @@ curl http://127.0.0.1:8787/v1/responses \
|
|
|
210
273
|
-d '{"model":"gpt-5.4","instructions":"你是一个简洁助手","input":"请只回复 OK"}'
|
|
211
274
|
```
|
|
212
275
|
|
|
276
|
+
兼容 `chat.completions` 的最小请求示例:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
curl http://127.0.0.1:8787/v1/chat/completions \
|
|
280
|
+
-H "content-type: application/json" \
|
|
281
|
+
-d '{
|
|
282
|
+
"model": "gpt-5.4",
|
|
283
|
+
"messages": [
|
|
284
|
+
{
|
|
285
|
+
"role": "user",
|
|
286
|
+
"content": "请只回复 OK"
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
}'
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 5. 调用生图接口
|
|
293
|
+
|
|
294
|
+
OpenAI 风格 `images.generations` 示例:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
curl http://127.0.0.1:8787/v1/images/generations \
|
|
298
|
+
-H "content-type: application/json" \
|
|
299
|
+
-d '{
|
|
300
|
+
"model": "gpt-image-2",
|
|
301
|
+
"prompt": "生成一张白底红苹果商品图,构图简洁,光线干净。",
|
|
302
|
+
"size": "1024x1024",
|
|
303
|
+
"quality": "low",
|
|
304
|
+
"response_format": "b64_json"
|
|
305
|
+
}'
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
响应会返回 OpenAI 同类型结构的 `data[].b64_json`。如果你在管理页里测试,这张图片会直接显示预览。
|
|
309
|
+
如果请求里不显式传 `model`,当前默认会使用 `gpt-image-2`。
|
|
310
|
+
|
|
311
|
+
生图能力和账号套餐有关:
|
|
312
|
+
|
|
313
|
+
- `plus` 或更高套餐账号可正常调用 `images.generations`
|
|
314
|
+
- `free` 账号不支持生图,网关会直接返回明确错误,而不是继续请求上游
|
|
315
|
+
- 管理页会显示当前账号的 `plan` 和“生图能力”状态
|
|
316
|
+
- 当当前账号是 `free` 且你选中 `Images` 测试时,“发送请求”按钮会被直接禁用
|
|
317
|
+
|
|
213
318
|
### 6. 当前支持的接口
|
|
214
319
|
|
|
215
320
|
- `GET /_gateway/health`
|
|
216
321
|
- `GET /_gateway/status`
|
|
217
322
|
- `GET /_gateway/models`
|
|
323
|
+
- `GET /_gateway/admin/config`
|
|
324
|
+
- `POST /_gateway/admin/login`
|
|
325
|
+
- `POST /_gateway/admin/logout`
|
|
326
|
+
- `POST /_gateway/admin/profiles/activate`
|
|
327
|
+
- `POST /_gateway/admin/profiles/remove`
|
|
328
|
+
- `PUT /_gateway/admin/settings`
|
|
218
329
|
- `GET /v1/models`
|
|
219
330
|
- `POST /v1/responses`
|
|
331
|
+
- `POST /v1/chat/completions`
|
|
332
|
+
- `POST /v1/images/generations`
|
|
220
333
|
|
|
221
334
|
### 7. 当前支持的主要参数
|
|
222
335
|
|
|
@@ -226,14 +339,78 @@ curl http://127.0.0.1:8787/v1/responses \
|
|
|
226
339
|
- `input`
|
|
227
340
|
- `instructions`
|
|
228
341
|
- `stream`
|
|
342
|
+
- `tools`
|
|
343
|
+
- `tool_choice`
|
|
344
|
+
- `include`
|
|
345
|
+
- `text`
|
|
346
|
+
- `store`
|
|
347
|
+
- `parallel_tool_calls`
|
|
348
|
+
- `experimental_codex.body`
|
|
349
|
+
- `experimental_codex.allow_unknown_model`
|
|
350
|
+
- `experimental_codex.include_raw`
|
|
351
|
+
|
|
352
|
+
`POST /v1/chat/completions` 当前主要支持:
|
|
353
|
+
|
|
354
|
+
- `model`
|
|
355
|
+
- `messages`
|
|
356
|
+
- `tools`
|
|
357
|
+
- `tool_choice`
|
|
358
|
+
- `response_format`
|
|
359
|
+
- `temperature`
|
|
360
|
+
- `top_p`
|
|
361
|
+
- `presence_penalty`
|
|
362
|
+
- `frequency_penalty`
|
|
363
|
+
- `metadata`
|
|
364
|
+
- `stop`
|
|
365
|
+
- `store`
|
|
366
|
+
- `parallel_tool_calls`
|
|
367
|
+
|
|
368
|
+
`POST /v1/images/generations` 当前主要支持:
|
|
369
|
+
|
|
370
|
+
- `prompt`
|
|
371
|
+
- `model`
|
|
372
|
+
- `n`
|
|
373
|
+
- `size`
|
|
374
|
+
- `quality`
|
|
375
|
+
- `background`
|
|
376
|
+
- `output_format`
|
|
377
|
+
- `output_compression`
|
|
378
|
+
- `moderation`
|
|
379
|
+
- `response_format`
|
|
380
|
+
- `user`
|
|
229
381
|
|
|
230
382
|
### 8. 当前限制
|
|
231
383
|
|
|
232
384
|
- `stream=true` 目前只识别,不返回真实流式结果
|
|
233
385
|
- 还没有完整覆盖 OpenAI Responses API 的全部字段
|
|
234
|
-
-
|
|
386
|
+
- `chat.completions` 暂不支持 `n > 1`
|
|
387
|
+
- `images.generations` 暂不支持 `n > 1`
|
|
388
|
+
- `images.generations` 当前只返回 `b64_json`,暂不支持托管图片 `url`
|
|
389
|
+
- `images.generations` 当前只透传 GPT Image 路径,不兼容 DALL·E 专有参数
|
|
390
|
+
- `images.generations` 对账号套餐有要求;`free` 账号会被网关直接拦截并返回“不支持图片生成”
|
|
235
391
|
- 网关当前默认面向本地单用户使用
|
|
236
392
|
|
|
393
|
+
## 兼容说明
|
|
394
|
+
|
|
395
|
+
代码里仍然保留了 `login`、`status`、`models`、`ask`、`serve`、`clear` 等 CLI 命令,主要用于调试、兼容和后续扩展。
|
|
396
|
+
|
|
397
|
+
README 不再把这些命令作为推荐使用方式。默认使用路径就是:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
azt start
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## 交流与反馈
|
|
404
|
+
|
|
405
|
+
如果你在使用过程中遇到安装问题、账号切换问题、生图异常,或者想交流自己的接入场景,可以通过下面两种方式联系我:
|
|
406
|
+
|
|
407
|
+
- GitHub Issues: [https://github.com/fchangjun/AI-Zero-Token/issues](https://github.com/fchangjun/AI-Zero-Token/issues)
|
|
408
|
+
- 微信交流:先加我微信,备注 `AI Zero Token`,我会再拉你进交流群
|
|
409
|
+
|
|
410
|
+
如果你已经把二维码放到仓库里,可以直接查看:
|
|
411
|
+
|
|
412
|
+

|
|
413
|
+
|
|
237
414
|
## 本地状态
|
|
238
415
|
|
|
239
416
|
项目会在仓库目录下写入:
|
|
@@ -243,7 +420,7 @@ curl http://127.0.0.1:8787/v1/responses \
|
|
|
243
420
|
|
|
244
421
|
它们分别用于保存:
|
|
245
422
|
|
|
246
|
-
- OAuth
|
|
423
|
+
- OAuth 认证信息和多个本地账号 profile
|
|
247
424
|
- 默认模型和服务配置
|
|
248
425
|
|
|
249
426
|
## 项目结构
|
package/dist/api.js
CHANGED
package/dist/cli/commands/ask.js
CHANGED
|
@@ -1,15 +1,141 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import { createGatewayContext } from "../../core/context.js";
|
|
3
5
|
import { parseAskArgs } from "../shared.js";
|
|
6
|
+
function collectEncodedArtifacts(value, trail = [], items = []) {
|
|
7
|
+
if (Array.isArray(value)) {
|
|
8
|
+
value.forEach((item, index) => {
|
|
9
|
+
collectEncodedArtifacts(item, [...trail, String(index)], items);
|
|
10
|
+
});
|
|
11
|
+
return items;
|
|
12
|
+
}
|
|
13
|
+
if (!value || typeof value !== "object") {
|
|
14
|
+
return items;
|
|
15
|
+
}
|
|
16
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
17
|
+
const nextTrail = [...trail, key];
|
|
18
|
+
if (typeof nested === "string" && /(?:^|_)image_b64$/i.test(key) && /^[A-Za-z0-9+/=\r\n]+$/.test(nested) && nested.length > 100) {
|
|
19
|
+
items.push({
|
|
20
|
+
key,
|
|
21
|
+
path: nextTrail.join("."),
|
|
22
|
+
value: nested.replace(/\s+/g, "")
|
|
23
|
+
});
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
collectEncodedArtifacts(nested, nextTrail, items);
|
|
27
|
+
}
|
|
28
|
+
return items;
|
|
29
|
+
}
|
|
30
|
+
function detectImageExtension(buffer) {
|
|
31
|
+
if (buffer.length >= 8 && buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71) {
|
|
32
|
+
return "png";
|
|
33
|
+
}
|
|
34
|
+
if (buffer.length >= 3 && buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) {
|
|
35
|
+
return "jpg";
|
|
36
|
+
}
|
|
37
|
+
if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
|
|
38
|
+
return "webp";
|
|
39
|
+
}
|
|
40
|
+
if (buffer.length >= 4 && buffer.subarray(0, 4).toString("ascii") === "GIF8") {
|
|
41
|
+
return "gif";
|
|
42
|
+
}
|
|
43
|
+
return "bin";
|
|
44
|
+
}
|
|
45
|
+
async function writeEncodedArtifacts(raw, outputDir) {
|
|
46
|
+
const encodedArtifacts = collectEncodedArtifacts(raw);
|
|
47
|
+
if (encodedArtifacts.length === 0) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
await mkdir(outputDir, { recursive: true });
|
|
51
|
+
const writtenFiles = [];
|
|
52
|
+
for (const [index, artifact] of encodedArtifacts.entries()) {
|
|
53
|
+
const buffer = Buffer.from(artifact.value, "base64");
|
|
54
|
+
const extension = detectImageExtension(buffer);
|
|
55
|
+
const safeName = artifact.path.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
56
|
+
const filename = `${String(index + 1).padStart(2, "0")}-${safeName}.${extension}`;
|
|
57
|
+
const target = path.join(outputDir, filename);
|
|
58
|
+
await writeFile(target, buffer);
|
|
59
|
+
writtenFiles.push(target);
|
|
60
|
+
}
|
|
61
|
+
return writtenFiles;
|
|
62
|
+
}
|
|
4
63
|
async function runAskCommand(args) {
|
|
5
|
-
const {
|
|
6
|
-
|
|
7
|
-
|
|
64
|
+
const {
|
|
65
|
+
model,
|
|
66
|
+
prompt,
|
|
67
|
+
payloadFile,
|
|
68
|
+
dumpRawFile,
|
|
69
|
+
writeArtifactsDir,
|
|
70
|
+
printRaw,
|
|
71
|
+
allowUnknownModel
|
|
72
|
+
} = parseAskArgs(args);
|
|
73
|
+
let codexBody;
|
|
74
|
+
if (payloadFile) {
|
|
75
|
+
const content = await readFile(payloadFile, "utf8");
|
|
76
|
+
const parsed = JSON.parse(content);
|
|
77
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
78
|
+
throw new Error(`payload \u6587\u4EF6\u5FC5\u987B\u662F JSON object: ${payloadFile}`);
|
|
79
|
+
}
|
|
80
|
+
codexBody = parsed;
|
|
81
|
+
}
|
|
82
|
+
if (!prompt && !codexBody) {
|
|
83
|
+
throw new Error('ask \u9700\u8981\u4E00\u4E2A prompt\uFF0C\u4F8B\u5982 azt ask "\u4F60\u597D"\uFF0C\u6216\u8005\u901A\u8FC7 --payload-file \u4F20\u5B9E\u9A8C\u8BF7\u6C42\u4F53');
|
|
8
84
|
}
|
|
9
85
|
const ctx = createGatewayContext();
|
|
10
|
-
|
|
86
|
+
const result = await ctx.chatService.chat({
|
|
87
|
+
model,
|
|
88
|
+
input: prompt || void 0,
|
|
89
|
+
experimental: {
|
|
90
|
+
codexBody,
|
|
91
|
+
allowUnknownModel: allowUnknownModel || Boolean(codexBody)
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
console.log(`provider: ${result.provider}`);
|
|
95
|
+
console.log(`model: ${result.model}`);
|
|
96
|
+
console.log("\u6A21\u578B\u56DE\u590D:");
|
|
97
|
+
console.log(result.text || "(\u8FD4\u56DE\u6210\u529F\uFF0C\u4F46\u6CA1\u6709\u89E3\u6790\u51FA output_text)");
|
|
98
|
+
if (result.artifacts.length > 0) {
|
|
99
|
+
console.log("\u5019\u9009\u4EA7\u7269\u5F15\u7528:");
|
|
100
|
+
for (const artifact of result.artifacts) {
|
|
101
|
+
console.log(`- [${artifact.source}] ${artifact.path} = ${artifact.value}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (writeArtifactsDir) {
|
|
105
|
+
const writtenFiles = await writeEncodedArtifacts(result.raw, writeArtifactsDir);
|
|
106
|
+
if (writtenFiles.length === 0) {
|
|
107
|
+
console.log("\u672A\u53D1\u73B0\u53EF\u5199\u51FA\u7684 base64 \u56FE\u7247\u4EA7\u7269\u3002");
|
|
108
|
+
} else {
|
|
109
|
+
console.log("\u56FE\u7247\u4EA7\u7269\u5DF2\u5199\u5165:");
|
|
110
|
+
for (const file of writtenFiles) {
|
|
111
|
+
console.log(`- ${file}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (dumpRawFile) {
|
|
116
|
+
await writeFile(
|
|
117
|
+
dumpRawFile,
|
|
118
|
+
`${JSON.stringify(
|
|
119
|
+
{
|
|
120
|
+
provider: result.provider,
|
|
121
|
+
model: result.model,
|
|
122
|
+
text: result.text,
|
|
123
|
+
artifacts: result.artifacts,
|
|
124
|
+
raw: result.raw
|
|
125
|
+
},
|
|
126
|
+
null,
|
|
127
|
+
2
|
|
128
|
+
)}
|
|
129
|
+
`,
|
|
130
|
+
"utf8"
|
|
131
|
+
);
|
|
132
|
+
console.log(`raw \u5DF2\u5199\u5165: ${dumpRawFile}`);
|
|
133
|
+
}
|
|
134
|
+
if (printRaw) {
|
|
135
|
+
console.log("raw:");
|
|
136
|
+
console.log(JSON.stringify(result.raw, null, 2));
|
|
137
|
+
}
|
|
11
138
|
}
|
|
12
139
|
export {
|
|
13
140
|
runAskCommand
|
|
14
141
|
};
|
|
15
|
-
//# sourceMappingURL=ask.js.map
|
|
@@ -2,20 +2,26 @@
|
|
|
2
2
|
function printHelp() {
|
|
3
3
|
console.log(`\u7528\u6CD5:
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
azt login
|
|
6
|
+
azt models
|
|
7
|
+
azt status
|
|
8
|
+
azt ask "\u4F60\u597D\uFF0C\u8BF7\u7B80\u5355\u4ECB\u7ECD\u4E00\u4E0B\u81EA\u5DF1"
|
|
9
|
+
azt ask --model gpt-5.3-codex "\u4F60\u597D"
|
|
10
|
+
azt ask --payload-file ./codex-body.json --dump-raw ./codex-raw.json
|
|
11
|
+
azt ask --payload-file ./codex-body.json --write-artifacts-dir ./artifacts
|
|
12
|
+
azt start
|
|
13
|
+
azt serve
|
|
14
|
+
azt clear
|
|
12
15
|
|
|
13
16
|
\u8BF4\u660E:
|
|
14
17
|
|
|
15
|
-
login \u8D70\u771F\u5B9E OpenAI Codex OAuth\uFF0C\u4FDD\u5B58
|
|
18
|
+
login \u8D70\u771F\u5B9E OpenAI Codex OAuth\uFF0C\u65B0\u589E\u5E76\u4FDD\u5B58\u4E00\u4E2A\u8D26\u53F7 profile
|
|
16
19
|
models \u67E5\u770B\u8FD9\u4E2A demo \u5F53\u524D\u5185\u7F6E\u652F\u6301\u7684\u6A21\u578B\u5217\u8868
|
|
17
|
-
status \u67E5\u770B\u5F53\u524D demo \
|
|
20
|
+
status \u67E5\u770B\u5F53\u524D demo \u5F53\u524D\u6FC0\u6D3B\u8D26\u53F7\u3001\u8D26\u53F7\u6570\u91CF\u548C\u8FC7\u671F\u65F6\u95F4
|
|
18
21
|
ask \u7528\u4FDD\u5B58\u7684 token \u8C03\u771F\u5B9E Codex Responses API
|
|
22
|
+
\u5B9E\u9A8C\u6A21\u5F0F\u53EF\u7528 --payload-file \u900F\u4F20\u989D\u5916\u8BF7\u6C42\u4F53\uFF0C\u914D\u5408 --dump-raw / --print-raw \u89C2\u5BDF SSE \u539F\u59CB\u4E8B\u4EF6
|
|
23
|
+
\u5982\u54CD\u5E94\u91CC\u542B image_b64 / partial_image_b64\uFF0C\u53EF\u7528 --write-artifacts-dir \u76F4\u63A5\u5199\u6210\u56FE\u7247\u6587\u4EF6
|
|
24
|
+
start \u542F\u52A8\u672C\u5730 HTTP \u7F51\u5173\u5E76\u81EA\u52A8\u6253\u5F00\u7BA1\u7406\u9875\u9762
|
|
19
25
|
serve \u542F\u52A8\u672C\u5730 HTTP \u7F51\u5173
|
|
20
26
|
clear \u6E05\u7A7A demo \u7684\u672C\u5730\u72B6\u6001
|
|
21
27
|
`);
|
|
@@ -23,4 +29,3 @@ function printHelp() {
|
|
|
23
29
|
export {
|
|
24
30
|
printHelp
|
|
25
31
|
};
|
|
26
|
-
//# sourceMappingURL=help.js.map
|