ai-zero-token 1.0.1 → 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.
Files changed (67) hide show
  1. package/README.md +227 -70
  2. package/dist/api.js +0 -1
  3. package/dist/cli/commands/ask.js +131 -5
  4. package/dist/cli/commands/clear.js +0 -1
  5. package/dist/cli/commands/help.js +15 -10
  6. package/dist/cli/commands/login.js +0 -1
  7. package/dist/cli/commands/models.js +0 -1
  8. package/dist/cli/commands/serve.js +41 -4
  9. package/dist/cli/commands/start.js +10 -0
  10. package/dist/cli/commands/status.js +1 -1
  11. package/dist/cli/index.js +4 -1
  12. package/dist/cli/shared.js +57 -6
  13. package/dist/cli.js +0 -1
  14. package/dist/core/context.js +7 -2
  15. package/dist/core/models/openai-codex-models.js +0 -1
  16. package/dist/core/providers/http-client.js +97 -9
  17. package/dist/core/providers/openai-codex/chat.js +217 -24
  18. package/dist/core/providers/openai-codex/oauth.js +15 -4
  19. package/dist/core/providers/openai-codex/pkce.js +0 -1
  20. package/dist/core/services/auth-service.js +89 -16
  21. package/dist/core/services/chat-service.js +24 -14
  22. package/dist/core/services/config-service.js +0 -1
  23. package/dist/core/services/image-service.js +360 -0
  24. package/dist/core/services/model-service.js +4 -2
  25. package/dist/core/store/profile-store.js +79 -6
  26. package/dist/core/store/settings-store.js +1 -2
  27. package/dist/core/types.js +0 -1
  28. package/dist/http.js +0 -1
  29. package/dist/models.js +0 -1
  30. package/dist/oauth.js +0 -1
  31. package/dist/pkce.js +0 -1
  32. package/dist/server/admin-page.js +2615 -0
  33. package/dist/server/app.js +561 -39
  34. package/dist/server/index.js +0 -1
  35. package/dist/store.js +0 -1
  36. package/package.json +12 -3
  37. package/dist/api.js.map +0 -1
  38. package/dist/cli/commands/ask.js.map +0 -1
  39. package/dist/cli/commands/clear.js.map +0 -1
  40. package/dist/cli/commands/help.js.map +0 -1
  41. package/dist/cli/commands/login.js.map +0 -1
  42. package/dist/cli/commands/models.js.map +0 -1
  43. package/dist/cli/commands/serve.js.map +0 -1
  44. package/dist/cli/commands/status.js.map +0 -1
  45. package/dist/cli/index.js.map +0 -1
  46. package/dist/cli/shared.js.map +0 -1
  47. package/dist/cli.js.map +0 -1
  48. package/dist/core/context.js.map +0 -1
  49. package/dist/core/models/openai-codex-models.js.map +0 -1
  50. package/dist/core/providers/http-client.js.map +0 -1
  51. package/dist/core/providers/openai-codex/chat.js.map +0 -1
  52. package/dist/core/providers/openai-codex/oauth.js.map +0 -1
  53. package/dist/core/providers/openai-codex/pkce.js.map +0 -1
  54. package/dist/core/services/auth-service.js.map +0 -1
  55. package/dist/core/services/chat-service.js.map +0 -1
  56. package/dist/core/services/config-service.js.map +0 -1
  57. package/dist/core/services/model-service.js.map +0 -1
  58. package/dist/core/store/profile-store.js.map +0 -1
  59. package/dist/core/store/settings-store.js.map +0 -1
  60. package/dist/core/types.js.map +0 -1
  61. package/dist/http.js.map +0 -1
  62. package/dist/models.js.map +0 -1
  63. package/dist/oauth.js.map +0 -1
  64. package/dist/pkce.js.map +0 -1
  65. package/dist/server/app.js.map +0 -1
  66. package/dist/server/index.js.map +0 -1
  67. 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
+ ## 这次迭代亮点
6
17
 
7
- 让个人用户优先复用自己已经拥有的账号订阅和账号授权能力,在本地直接接入高质量 LLM,而不是再单独为优质 API 额度和接口调用链付费。
18
+ - 直接代理 `gpt-image-2`,把图片生成能力暴露成 OpenAI 风格 `images.generations` 接口
19
+ - 启动 `azt start` 后即可获得本地管理页和本地网关,适合脚本、前端和自动化流程接入
20
+ - 支持多账号保存、切换当前账号、查看账号套餐 plan,以及当前账号是否支持生图
21
+ - `free` 账号会在管理页直接预警,并在网关层明确拦截生图请求
8
22
 
9
- 很多用户已经有 ChatGPT、Claude、Gemini 之类产品的账号、订阅或可用授权能力,但缺少一个统一、可脚本化、可本地集成的入口。AI Zero Token 要做的,就是把这类已有授权能力整理成一个可直接使用的命令行工具和本地接口。
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
+ ![AI Zero Token 管理页预览](docs/images/admin-dashboard.jpg)
43
+
44
+ ## 生图结果预览
45
+
46
+ 下图通过 AI Zero Token 本地网关调用 `POST /v1/images/generations` 生成,模型为 `gpt-image-2`。
47
+
48
+ ![AI Zero Token gpt-image-2 生图结果预览](docs/images/gpt-image-2-preview.png)
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
+ ![AI Zero Token 实验工具架构图](docs/images/architecture-diagram.png)
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
- - 通过 CLI 发起真实模型请求
28
- - 启动本地 HTTP 网关
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 help
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 help
133
+ azt start
80
134
  ```
81
135
 
82
136
  如果你是为了开发、构建、`npm link`、`npm pack` 或准备发布,单独看:
@@ -85,39 +139,39 @@ azt help
85
139
 
86
140
  ## 快速开始
87
141
 
88
- 登录:
89
- ```bash
90
- azt login
91
- ```
92
-
93
- 查看当前状态:
142
+ 启动本地网关和管理页面:
94
143
 
95
144
  ```bash
96
- azt status
145
+ azt start
97
146
  ```
98
147
 
99
- 查看支持的模型:
148
+ 执行后会自动打开管理页,默认地址:
100
149
 
101
- ```bash
102
- azt models
150
+ ```text
151
+ http://127.0.0.1:8787
103
152
  ```
104
153
 
105
- 发起一次对话:
154
+ 默认会监听:
106
155
 
107
- ```bash
108
- azt ask "请只回复 OK"
156
+ ```text
157
+ 0.0.0.0:8787
109
158
  ```
110
159
 
111
- 指定模型发起对话:
160
+ 这表示本机可以用 `127.0.0.1:8787` 访问,局域网内其他设备也可以用你的机器 IP 访问,比如 `http://172.26.66.132:8787`。
112
161
 
113
- ```bash
114
- azt ask --model gpt-5.3-codex "请只回复 OK"
115
- ```
162
+ 接下来在管理页里完成这几件事:
116
163
 
117
- 启动本地网关:
164
+ - 登录一个或多个 OpenAI Codex 账号
165
+ - 查看当前账号状态和过期时间
166
+ - 在已保存账号之间切换当前使用账号
167
+ - 切换默认模型
168
+ - 直接测试 `models`、`responses`、`chat.completions`
169
+ - 直接测试 `images.generations` 生图接口
118
170
 
119
- ```bash
120
- azt serve
171
+ 如果你要把它接到自己的客户端,只需要把 Base URL 指向:
172
+
173
+ ```text
174
+ http://127.0.0.1:8787/v1
121
175
  ```
122
176
 
123
177
  如果你要让本地网页直接从浏览器请求这个网关,现在已经默认开启 CORS。
@@ -125,19 +179,13 @@ azt serve
125
179
  如需限制来源,可以在启动前指定:
126
180
 
127
181
  ```bash
128
- AZT_CORS_ORIGIN=http://127.0.0.1:8124 azt serve
182
+ AZT_CORS_ORIGIN=http://127.0.0.1:8124 azt start
129
183
  ```
130
184
 
131
185
  多个来源可用英文逗号分隔:
132
186
 
133
187
  ```bash
134
- AZT_CORS_ORIGIN=http://127.0.0.1:8124,http://localhost:3000 azt serve
135
- ```
136
-
137
- 清空本地状态:
138
-
139
- ```bash
140
- azt clear
188
+ AZT_CORS_ORIGIN=http://127.0.0.1:8124,http://localhost:3000 azt start
141
189
  ```
142
190
 
143
191
  如果你当前还没有全局命令,也可以把上面的 `azt` 临时替换成:
@@ -146,34 +194,39 @@ azt clear
146
194
  bun src/cli.ts
147
195
  ```
148
196
 
149
- 例如:
150
-
151
- ```bash
152
- bun src/cli.ts login
153
- ```
197
+ 例如:`bun src/cli.ts start`
154
198
 
155
199
  ## 网关使用说明
156
200
 
157
- 如果你主要把 AI Zero Token 当作本地网关来使用,建议按下面的顺序操作。
201
+ 如果你主要把 AI Zero Token 当作本地网关来使用,推荐只记住一个命令:
158
202
 
159
- ### 1. 先完成登录
203
+ ### 1. 启动
160
204
 
161
205
  ```bash
162
- azt login
206
+ azt start
163
207
  ```
164
208
 
165
- 这一步会打开浏览器,完成 OpenAI Codex OAuth 登录,并把可用 token 保存到本地。
166
-
167
- ### 2. 启动本地网关
209
+ 启动后会自动打开管理页。管理页就是默认工作入口,你可以在里面直接:
168
210
 
169
- ```bash
170
- azt serve
171
- ```
211
+ - 触发 OpenAI Codex OAuth 登录并新增账号
212
+ - 查看当前账号、已保存账号列表、过期时间、token 摘要
213
+ - 查看账号套餐 plan 和当前账号是否支持生图
214
+ - 在多个已保存账号之间切换当前使用账号
215
+ - 删除单个本地账号,或一键清空全部本地账号
216
+ - 切换默认模型
217
+ - 测试 `models` / `responses` / `chat.completions`
218
+ - 测试 `images.generations`
172
219
 
173
- CLI 一旦执行 `serve`,就会进入本地网关模式。
220
+ 管理页里邮箱默认脱敏显示,需要手动点击“查看邮箱”才会显示明文。
174
221
 
175
222
  默认监听地址:
176
223
 
224
+ ```text
225
+ http://0.0.0.0:8787
226
+ ```
227
+
228
+ 本机浏览器访问:
229
+
177
230
  ```text
178
231
  http://127.0.0.1:8787
179
232
  ```
@@ -184,27 +237,17 @@ http://127.0.0.1:8787
184
237
  *
185
238
  ```
186
239
 
187
- ### 3. 先确认网关状态
188
-
189
- 健康检查:
190
-
191
- ```bash
192
- curl http://127.0.0.1:8787/_gateway/health
193
- ```
240
+ ### 2. 把它接到你的客户端
194
241
 
195
- 查看当前网关状态:
242
+ 客户端或 SDK 里把 Base URL 改成:
196
243
 
197
- ```bash
198
- curl http://127.0.0.1:8787/_gateway/status
244
+ ```text
245
+ http://127.0.0.1:8787/v1
199
246
  ```
200
247
 
201
- ### 4. 查看模型列表
202
-
203
- 内部模型接口:
248
+ 如果客户端必须填写 API Key,可以填任意非空占位值;真正起作用的是本地网关地址。
204
249
 
205
- ```bash
206
- curl http://127.0.0.1:8787/_gateway/models
207
- ```
250
+ ### 3. 查看模型列表
208
251
 
209
252
  OpenAI 风格模型接口:
210
253
 
@@ -212,7 +255,7 @@ OpenAI 风格模型接口:
212
255
  curl http://127.0.0.1:8787/v1/models
213
256
  ```
214
257
 
215
- ### 5. 调用对话接口
258
+ ### 4. 调用对话接口
216
259
 
217
260
  最小请求示例:
218
261
 
@@ -230,13 +273,63 @@ curl http://127.0.0.1:8787/v1/responses \
230
273
  -d '{"model":"gpt-5.4","instructions":"你是一个简洁助手","input":"请只回复 OK"}'
231
274
  ```
232
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
+
233
318
  ### 6. 当前支持的接口
234
319
 
235
320
  - `GET /_gateway/health`
236
321
  - `GET /_gateway/status`
237
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`
238
329
  - `GET /v1/models`
239
330
  - `POST /v1/responses`
331
+ - `POST /v1/chat/completions`
332
+ - `POST /v1/images/generations`
240
333
 
241
334
  ### 7. 当前支持的主要参数
242
335
 
@@ -246,14 +339,78 @@ curl http://127.0.0.1:8787/v1/responses \
246
339
  - `input`
247
340
  - `instructions`
248
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`
249
381
 
250
382
  ### 8. 当前限制
251
383
 
252
384
  - `stream=true` 目前只识别,不返回真实流式结果
253
385
  - 还没有完整覆盖 OpenAI Responses API 的全部字段
254
- - 还没有实现 `/v1/chat/completions`
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` 账号会被网关直接拦截并返回“不支持图片生成”
255
391
  - 网关当前默认面向本地单用户使用
256
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
+ ![AI Zero Token 微信联系二维码](docs/images/wechat-contact.png)
413
+
257
414
  ## 本地状态
258
415
 
259
416
  项目会在仓库目录下写入:
@@ -263,7 +420,7 @@ curl http://127.0.0.1:8787/v1/responses \
263
420
 
264
421
  它们分别用于保存:
265
422
 
266
- - OAuth 认证信息
423
+ - OAuth 认证信息和多个本地账号 profile
267
424
  - 默认模型和服务配置
268
425
 
269
426
  ## 项目结构
package/dist/api.js CHANGED
@@ -3,4 +3,3 @@ import { askOpenAICodex } from "./core/providers/openai-codex/chat.js";
3
3
  export {
4
4
  askOpenAICodex
5
5
  };
6
- //# sourceMappingURL=api.js.map
@@ -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 { model, prompt } = parseAskArgs(args);
6
- if (!prompt) {
7
- throw new Error('ask \u9700\u8981\u4E00\u4E2A prompt\uFF0C\u4F8B\u5982 bun src/cli.js ask "\u4F60\u597D"');
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
- console.log("model:", ctx);
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
@@ -8,4 +8,3 @@ async function runClearCommand() {
8
8
  export {
9
9
  runClearCommand
10
10
  };
11
- //# sourceMappingURL=clear.js.map
@@ -2,20 +2,26 @@
2
2
  function printHelp() {
3
3
  console.log(`\u7528\u6CD5:
4
4
 
5
- bun src/cli.js login
6
- bun src/cli.js models
7
- bun src/cli.js status
8
- bun src/cli.js ask "\u4F60\u597D\uFF0C\u8BF7\u7B80\u5355\u4ECB\u7ECD\u4E00\u4E0B\u81EA\u5DF1"
9
- bun src/cli.js ask --model gpt-5.3-codex "\u4F60\u597D"
10
- bun src/cli.js serve
11
- bun src/cli.js clear
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 access/refresh token
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 \u4FDD\u5B58\u7684\u8D26\u53F7\u548C\u8FC7\u671F\u65F6\u95F4
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
@@ -18,4 +18,3 @@ async function runLoginCommand() {
18
18
  export {
19
19
  runLoginCommand
20
20
  };
21
- //# sourceMappingURL=login.js.map
@@ -11,4 +11,3 @@ async function runModelsCommand() {
11
11
  export {
12
12
  runModelsCommand
13
13
  };
14
- //# sourceMappingURL=models.js.map