ai-zero-token 1.0.1 → 1.0.3

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 (70) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +235 -69
  3. package/dist/api.js +0 -1
  4. package/dist/cli/commands/ask.js +131 -5
  5. package/dist/cli/commands/clear.js +0 -1
  6. package/dist/cli/commands/help.js +17 -11
  7. package/dist/cli/commands/login.js +0 -1
  8. package/dist/cli/commands/models.js +14 -4
  9. package/dist/cli/commands/serve.js +41 -4
  10. package/dist/cli/commands/start.js +10 -0
  11. package/dist/cli/commands/status.js +1 -1
  12. package/dist/cli/index.js +5 -2
  13. package/dist/cli/shared.js +57 -6
  14. package/dist/cli.js +0 -1
  15. package/dist/core/context.js +10 -2
  16. package/dist/core/models/openai-codex-models.js +89 -1
  17. package/dist/core/providers/http-client.js +137 -14
  18. package/dist/core/providers/openai-codex/chat.js +217 -24
  19. package/dist/core/providers/openai-codex/oauth.js +15 -4
  20. package/dist/core/providers/openai-codex/pkce.js +0 -1
  21. package/dist/core/services/auth-service.js +125 -16
  22. package/dist/core/services/chat-service.js +24 -14
  23. package/dist/core/services/config-service.js +4 -5
  24. package/dist/core/services/image-service.js +405 -0
  25. package/dist/core/services/model-service.js +35 -8
  26. package/dist/core/services/version-service.js +97 -0
  27. package/dist/core/store/profile-store.js +79 -6
  28. package/dist/core/store/settings-store.js +1 -2
  29. package/dist/core/types.js +0 -1
  30. package/dist/http.js +0 -1
  31. package/dist/models.js +0 -1
  32. package/dist/oauth.js +0 -1
  33. package/dist/pkce.js +0 -1
  34. package/dist/server/admin-page.js +3165 -0
  35. package/dist/server/app.js +599 -40
  36. package/dist/server/index.js +0 -1
  37. package/dist/store.js +0 -1
  38. package/docs/API_USAGE.md +120 -0
  39. package/package.json +14 -3
  40. package/dist/api.js.map +0 -1
  41. package/dist/cli/commands/ask.js.map +0 -1
  42. package/dist/cli/commands/clear.js.map +0 -1
  43. package/dist/cli/commands/help.js.map +0 -1
  44. package/dist/cli/commands/login.js.map +0 -1
  45. package/dist/cli/commands/models.js.map +0 -1
  46. package/dist/cli/commands/serve.js.map +0 -1
  47. package/dist/cli/commands/status.js.map +0 -1
  48. package/dist/cli/index.js.map +0 -1
  49. package/dist/cli/shared.js.map +0 -1
  50. package/dist/cli.js.map +0 -1
  51. package/dist/core/context.js.map +0 -1
  52. package/dist/core/models/openai-codex-models.js.map +0 -1
  53. package/dist/core/providers/http-client.js.map +0 -1
  54. package/dist/core/providers/openai-codex/chat.js.map +0 -1
  55. package/dist/core/providers/openai-codex/oauth.js.map +0 -1
  56. package/dist/core/providers/openai-codex/pkce.js.map +0 -1
  57. package/dist/core/services/auth-service.js.map +0 -1
  58. package/dist/core/services/chat-service.js.map +0 -1
  59. package/dist/core/services/config-service.js.map +0 -1
  60. package/dist/core/services/model-service.js.map +0 -1
  61. package/dist/core/store/profile-store.js.map +0 -1
  62. package/dist/core/store/settings-store.js.map +0 -1
  63. package/dist/core/types.js.map +0 -1
  64. package/dist/http.js.map +0 -1
  65. package/dist/models.js.map +0 -1
  66. package/dist/oauth.js.map +0 -1
  67. package/dist/pkce.js.map +0 -1
  68. package/dist/server/app.js.map +0 -1
  69. package/dist/server/index.js.map +0 -1
  70. package/dist/store.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## 1.0.3 - 2026-04-24
4
+
5
+ - Added dynamic Codex model discovery from the local `~/.codex/models_cache.json` cache, with static model fallback when the cache is unavailable.
6
+ - Added `azt models --refresh` and a management-page action to re-read the local Codex model list without rebuilding the package.
7
+ - Added runtime version checks against npm, including a prominent update panel in the management UI when a newer version is available.
8
+ - Added 10-minute automatic refresh for quota snapshots and version status in the management UI.
9
+ - Improved quota display so account cards show used and remaining quota percentages clearly.
10
+ - Improved quota syncing so inactive or missing login state does not break runtime refresh.
11
+ - Improved image generation error handling with transient retries and clearer failure details.
12
+ - Preserved response headers when using the curl HTTP fallback so quota metadata can still be captured.
13
+ - Added Vibe Coding / OpenAI-compatible client integration documentation.
14
+
package/README.md CHANGED
@@ -1,12 +1,53 @@
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
+ - 模型列表会优先同步本机 `~/.codex/models_cache.json`,不需要每次为新模型重新 build
22
+ - 管理页会每 10 分钟自动同步额度快照和版本状态,并提示当前版本是否可更新
23
+ - `free` 账号会在管理页直接预警,并在网关层明确拦截生图请求
8
24
 
9
- 很多用户已经有 ChatGPT、Claude、Gemini 之类产品的账号、订阅或可用授权能力,但缺少一个统一、可脚本化、可本地集成的入口。AI Zero Token 要做的,就是把这类已有授权能力整理成一个可直接使用的命令行工具和本地接口。
25
+ 如果你只关心一句话,可以把这个项目理解为:
26
+
27
+ > 一个把账号授权能力代理成本地 OpenAI 风格接口的实验工具,这次重点补齐了 `gpt-image-2` 生图接口。
28
+
29
+ ## 一条命令开始
30
+
31
+ ```bash
32
+ npm install -g ai-zero-token
33
+ azt start
34
+ ```
35
+
36
+ 启动后会打开本地管理页,并暴露 OpenAI 风格接口:
37
+
38
+ ```text
39
+ http://127.0.0.1:8787/v1
40
+ ```
41
+
42
+ ## 界面预览
43
+
44
+ ![AI Zero Token 管理页预览](docs/images/admin-dashboard.jpg)
45
+
46
+ ## 生图结果预览
47
+
48
+ 下图通过 AI Zero Token 本地网关调用 `POST /v1/images/generations` 生成,模型为 `gpt-image-2`。
49
+
50
+ ![AI Zero Token gpt-image-2 生图结果预览](docs/images/gpt-image-2-preview.png)
10
51
 
11
52
  ## 为什么做这个项目
12
53
 
@@ -19,21 +60,37 @@ AI Zero Token 是一个本地优先的单用户 AI CLI 和本地网关。
19
60
 
20
61
  AI Zero Token 就是围绕这些问题设计的。
21
62
 
63
+ ## 它能解决什么
64
+
65
+ - 想把 ChatGPT 的图片生成能力接到自己的前端、小工具或脚本里
66
+ - 想先用本地网关验证产品原型,而不是一上来就重做整套后端
67
+ - 想统一成 OpenAI 风格接口,减少接入成本
68
+ - 想研究一条完整的 OAuth -> token -> 本地状态 -> 网关 -> 上游请求链路
69
+
70
+ ## 架构概览
71
+
72
+ ![AI Zero Token 实验工具架构图](docs/images/architecture-diagram.png)
73
+
22
74
  ## 当前能做什么
23
75
 
76
+ - 通过 `POST /v1/images/generations` 代理 `gpt-image-2` 生图请求
24
77
  - 通过 OpenAI Codex OAuth 登录
25
78
  - 在本地保存 `access_token` 和 `refresh_token`
79
+ - 支持保存多个账号 profile,并手动切换当前生效账号
26
80
  - 在 token 过期时自动刷新
27
- - 通过 CLI 发起真实模型请求
28
- - 启动本地 HTTP 网关
81
+ - 通过 `azt start` 一键启动本地 HTTP 网关和管理页面
82
+ - 在管理页面里完成多账号登录、查看账号状态、切换当前账号、切换默认模型、测试接口
83
+ - 模型列表优先读取本机 Codex 最新缓存,并支持在 CLI / 管理页手动同步
29
84
  - 暴露 OpenAI 风格接口:
30
85
  - `GET /v1/models`
31
86
  - `POST /v1/responses`
32
-
87
+ - `POST /v1/chat/completions`
88
+ - `POST /v1/images/generations`
33
89
 
34
90
  ## 适合谁用
35
91
 
36
92
  - 想把账号授权能力包装成本地工具的人
93
+ - 想把 `gpt-image-2` 生图接口直接接给脚本、前端或自动化流程的人
37
94
  - 想做自己的 AI 网关、AI CLI、AI 桌面端的人
38
95
  - 想学习一条完整 OAuth -> token -> CLI -> HTTP API 链路的人
39
96
  - 想把 AI 能力接入脚本、前端或自动化流程的人
@@ -62,7 +119,7 @@ npm install
62
119
  直接运行源码:
63
120
 
64
121
  ```bash
65
- bun src/cli.ts help
122
+ bun src/cli.ts start
66
123
  ```
67
124
 
68
125
  ### 从 npm 安装 CLI
@@ -76,7 +133,8 @@ npm install -g ai-zero-token
76
133
  安装后验证:
77
134
 
78
135
  ```bash
79
- azt help
136
+ azt start
137
+ azt models --refresh
80
138
  ```
81
139
 
82
140
  如果你是为了开发、构建、`npm link`、`npm pack` 或准备发布,单独看:
@@ -85,59 +143,57 @@ azt help
85
143
 
86
144
  ## 快速开始
87
145
 
88
- 登录:
146
+ 启动本地网关和管理页面:
147
+
89
148
  ```bash
90
- azt login
149
+ azt start
91
150
  ```
92
151
 
93
- 查看当前状态:
152
+ 执行后会自动打开管理页,默认地址:
94
153
 
95
- ```bash
96
- azt status
154
+ ```text
155
+ http://127.0.0.1:8787
97
156
  ```
98
157
 
99
- 查看支持的模型:
158
+ 默认会监听:
100
159
 
101
- ```bash
102
- azt models
160
+ ```text
161
+ 0.0.0.0:8787
103
162
  ```
104
163
 
105
- 发起一次对话:
164
+ 这表示本机可以用 `127.0.0.1:8787` 访问,局域网内其他设备也可以用你的机器 IP 访问,比如 `http://172.26.66.132:8787`。
106
165
 
107
- ```bash
108
- azt ask "请只回复 OK"
109
- ```
166
+ 接下来在管理页里完成这几件事:
110
167
 
111
- 指定模型发起对话:
168
+ - 登录一个或多个 OpenAI Codex 账号
169
+ - 查看当前账号状态和过期时间
170
+ - 在已保存账号之间切换当前使用账号
171
+ - 切换默认模型
172
+ - 直接测试 `models`、`responses`、`chat.completions`
173
+ - 直接测试 `images.generations` 生图接口
112
174
 
113
- ```bash
114
- azt ask --model gpt-5.3-codex "请只回复 OK"
175
+ 如果你要把它接到自己的客户端,只需要把 Base URL 指向:
176
+
177
+ ```text
178
+ http://127.0.0.1:8787/v1
115
179
  ```
116
180
 
117
- 启动本地网关:
181
+ Vibe Coding、OpenAI-compatible SDK 和脚本接入可以参考:
118
182
 
119
- ```bash
120
- azt serve
121
- ```
183
+ - [API 使用说明](docs/API_USAGE.md)
122
184
 
123
185
  如果你要让本地网页直接从浏览器请求这个网关,现在已经默认开启 CORS。
124
186
 
125
187
  如需限制来源,可以在启动前指定:
126
188
 
127
189
  ```bash
128
- AZT_CORS_ORIGIN=http://127.0.0.1:8124 azt serve
190
+ AZT_CORS_ORIGIN=http://127.0.0.1:8124 azt start
129
191
  ```
130
192
 
131
193
  多个来源可用英文逗号分隔:
132
194
 
133
195
  ```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
196
+ AZT_CORS_ORIGIN=http://127.0.0.1:8124,http://localhost:3000 azt start
141
197
  ```
142
198
 
143
199
  如果你当前还没有全局命令,也可以把上面的 `azt` 临时替换成:
@@ -146,34 +202,39 @@ azt clear
146
202
  bun src/cli.ts
147
203
  ```
148
204
 
149
- 例如:
150
-
151
- ```bash
152
- bun src/cli.ts login
153
- ```
205
+ 例如:`bun src/cli.ts start`
154
206
 
155
207
  ## 网关使用说明
156
208
 
157
- 如果你主要把 AI Zero Token 当作本地网关来使用,建议按下面的顺序操作。
209
+ 如果你主要把 AI Zero Token 当作本地网关来使用,推荐只记住一个命令:
158
210
 
159
- ### 1. 先完成登录
211
+ ### 1. 启动
160
212
 
161
213
  ```bash
162
- azt login
214
+ azt start
163
215
  ```
164
216
 
165
- 这一步会打开浏览器,完成 OpenAI Codex OAuth 登录,并把可用 token 保存到本地。
166
-
167
- ### 2. 启动本地网关
217
+ 启动后会自动打开管理页。管理页就是默认工作入口,你可以在里面直接:
168
218
 
169
- ```bash
170
- azt serve
171
- ```
219
+ - 触发 OpenAI Codex OAuth 登录并新增账号
220
+ - 查看当前账号、已保存账号列表、过期时间、token 摘要
221
+ - 查看账号套餐 plan 和当前账号是否支持生图
222
+ - 在多个已保存账号之间切换当前使用账号
223
+ - 删除单个本地账号,或一键清空全部本地账号
224
+ - 切换默认模型
225
+ - 测试 `models` / `responses` / `chat.completions`
226
+ - 测试 `images.generations`
172
227
 
173
- CLI 一旦执行 `serve`,就会进入本地网关模式。
228
+ 管理页里邮箱默认脱敏显示,需要手动点击“查看邮箱”才会显示明文。
174
229
 
175
230
  默认监听地址:
176
231
 
232
+ ```text
233
+ http://0.0.0.0:8787
234
+ ```
235
+
236
+ 本机浏览器访问:
237
+
177
238
  ```text
178
239
  http://127.0.0.1:8787
179
240
  ```
@@ -184,27 +245,17 @@ http://127.0.0.1:8787
184
245
  *
185
246
  ```
186
247
 
187
- ### 3. 先确认网关状态
188
-
189
- 健康检查:
190
-
191
- ```bash
192
- curl http://127.0.0.1:8787/_gateway/health
193
- ```
248
+ ### 2. 把它接到你的客户端
194
249
 
195
- 查看当前网关状态:
250
+ 客户端或 SDK 里把 Base URL 改成:
196
251
 
197
- ```bash
198
- curl http://127.0.0.1:8787/_gateway/status
252
+ ```text
253
+ http://127.0.0.1:8787/v1
199
254
  ```
200
255
 
201
- ### 4. 查看模型列表
202
-
203
- 内部模型接口:
256
+ 如果客户端必须填写 API Key,可以填任意非空占位值;真正起作用的是本地网关地址。
204
257
 
205
- ```bash
206
- curl http://127.0.0.1:8787/_gateway/models
207
- ```
258
+ ### 3. 查看模型列表
208
259
 
209
260
  OpenAI 风格模型接口:
210
261
 
@@ -212,7 +263,7 @@ OpenAI 风格模型接口:
212
263
  curl http://127.0.0.1:8787/v1/models
213
264
  ```
214
265
 
215
- ### 5. 调用对话接口
266
+ ### 4. 调用对话接口
216
267
 
217
268
  最小请求示例:
218
269
 
@@ -230,13 +281,64 @@ curl http://127.0.0.1:8787/v1/responses \
230
281
  -d '{"model":"gpt-5.4","instructions":"你是一个简洁助手","input":"请只回复 OK"}'
231
282
  ```
232
283
 
284
+ 兼容 `chat.completions` 的最小请求示例:
285
+
286
+ ```bash
287
+ curl http://127.0.0.1:8787/v1/chat/completions \
288
+ -H "content-type: application/json" \
289
+ -d '{
290
+ "model": "gpt-5.4",
291
+ "messages": [
292
+ {
293
+ "role": "user",
294
+ "content": "请只回复 OK"
295
+ }
296
+ ]
297
+ }'
298
+ ```
299
+
300
+ ### 5. 调用生图接口
301
+
302
+ OpenAI 风格 `images.generations` 示例:
303
+
304
+ ```bash
305
+ curl http://127.0.0.1:8787/v1/images/generations \
306
+ -H "content-type: application/json" \
307
+ -d '{
308
+ "model": "gpt-image-2",
309
+ "prompt": "生成一张白底红苹果商品图,构图简洁,光线干净。",
310
+ "size": "1024x1024",
311
+ "quality": "low",
312
+ "response_format": "b64_json"
313
+ }'
314
+ ```
315
+
316
+ 响应会返回 OpenAI 同类型结构的 `data[].b64_json`。如果你在管理页里测试,这张图片会直接显示预览。
317
+ 如果请求里不显式传 `model`,当前默认会使用 `gpt-image-2`。
318
+
319
+ 生图能力和账号套餐有关:
320
+
321
+ - `plus` 或更高套餐账号可正常调用 `images.generations`
322
+ - `free` 账号不支持生图,网关会直接返回明确错误,而不是继续请求上游
323
+ - 管理页会显示当前账号的 `plan` 和“生图能力”状态
324
+ - 当当前账号是 `free` 且你选中 `Images` 测试时,“发送请求”按钮会被直接禁用
325
+
233
326
  ### 6. 当前支持的接口
234
327
 
235
328
  - `GET /_gateway/health`
236
329
  - `GET /_gateway/status`
237
330
  - `GET /_gateway/models`
331
+ - `POST /_gateway/models/refresh`
332
+ - `GET /_gateway/admin/config`
333
+ - `POST /_gateway/admin/login`
334
+ - `POST /_gateway/admin/logout`
335
+ - `POST /_gateway/admin/profiles/activate`
336
+ - `POST /_gateway/admin/profiles/remove`
337
+ - `PUT /_gateway/admin/settings`
238
338
  - `GET /v1/models`
239
339
  - `POST /v1/responses`
340
+ - `POST /v1/chat/completions`
341
+ - `POST /v1/images/generations`
240
342
 
241
343
  ### 7. 当前支持的主要参数
242
344
 
@@ -246,14 +348,78 @@ curl http://127.0.0.1:8787/v1/responses \
246
348
  - `input`
247
349
  - `instructions`
248
350
  - `stream`
351
+ - `tools`
352
+ - `tool_choice`
353
+ - `include`
354
+ - `text`
355
+ - `store`
356
+ - `parallel_tool_calls`
357
+ - `experimental_codex.body`
358
+ - `experimental_codex.allow_unknown_model`
359
+ - `experimental_codex.include_raw`
360
+
361
+ `POST /v1/chat/completions` 当前主要支持:
362
+
363
+ - `model`
364
+ - `messages`
365
+ - `tools`
366
+ - `tool_choice`
367
+ - `response_format`
368
+ - `temperature`
369
+ - `top_p`
370
+ - `presence_penalty`
371
+ - `frequency_penalty`
372
+ - `metadata`
373
+ - `stop`
374
+ - `store`
375
+ - `parallel_tool_calls`
376
+
377
+ `POST /v1/images/generations` 当前主要支持:
378
+
379
+ - `prompt`
380
+ - `model`
381
+ - `n`
382
+ - `size`
383
+ - `quality`
384
+ - `background`
385
+ - `output_format`
386
+ - `output_compression`
387
+ - `moderation`
388
+ - `response_format`
389
+ - `user`
249
390
 
250
391
  ### 8. 当前限制
251
392
 
252
393
  - `stream=true` 目前只识别,不返回真实流式结果
253
394
  - 还没有完整覆盖 OpenAI Responses API 的全部字段
254
- - 还没有实现 `/v1/chat/completions`
395
+ - `chat.completions` 暂不支持 `n > 1`
396
+ - `images.generations` 暂不支持 `n > 1`
397
+ - `images.generations` 当前只返回 `b64_json`,暂不支持托管图片 `url`
398
+ - `images.generations` 当前只透传 GPT Image 路径,不兼容 DALL·E 专有参数
399
+ - `images.generations` 对账号套餐有要求;`free` 账号会被网关直接拦截并返回“不支持图片生成”
255
400
  - 网关当前默认面向本地单用户使用
256
401
 
402
+ ## 兼容说明
403
+
404
+ 代码里仍然保留了 `login`、`status`、`models`、`ask`、`serve`、`clear` 等 CLI 命令,主要用于调试、兼容和后续扩展。
405
+
406
+ README 不再把这些命令作为推荐使用方式。默认使用路径就是:
407
+
408
+ ```bash
409
+ azt start
410
+ ```
411
+
412
+ ## 交流与反馈
413
+
414
+ 如果你在使用过程中遇到安装问题、账号切换问题、生图异常,或者想交流自己的接入场景,可以通过下面两种方式联系我:
415
+
416
+ - GitHub Issues: [https://github.com/fchangjun/AI-Zero-Token/issues](https://github.com/fchangjun/AI-Zero-Token/issues)
417
+ - 微信交流:先加我微信,备注 `AI Zero Token`,我会再拉你进交流群
418
+
419
+ 如果你已经把二维码放到仓库里,可以直接查看:
420
+
421
+ ![AI Zero Token 微信联系二维码](docs/images/wechat-contact.png)
422
+
257
423
  ## 本地状态
258
424
 
259
425
  项目会在仓库目录下写入:
@@ -263,7 +429,7 @@ curl http://127.0.0.1:8787/v1/responses \
263
429
 
264
430
  它们分别用于保存:
265
431
 
266
- - OAuth 认证信息
432
+ - OAuth 认证信息和多个本地账号 profile
267
433
  - 默认模型和服务配置
268
434
 
269
435
  ## 项目结构
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,27 @@
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 models --refresh
8
+ azt status
9
+ azt ask "\u4F60\u597D\uFF0C\u8BF7\u7B80\u5355\u4ECB\u7ECD\u4E00\u4E0B\u81EA\u5DF1"
10
+ azt ask --model gpt-5.3-codex "\u4F60\u597D"
11
+ azt ask --payload-file ./codex-body.json --dump-raw ./codex-raw.json
12
+ azt ask --payload-file ./codex-body.json --write-artifacts-dir ./artifacts
13
+ azt start
14
+ azt serve
15
+ azt clear
12
16
 
13
17
  \u8BF4\u660E:
14
18
 
15
- login \u8D70\u771F\u5B9E OpenAI Codex OAuth\uFF0C\u4FDD\u5B58 access/refresh token
16
- 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
19
+ login \u8D70\u771F\u5B9E OpenAI Codex OAuth\uFF0C\u65B0\u589E\u5E76\u4FDD\u5B58\u4E00\u4E2A\u8D26\u53F7 profile
20
+ models \u67E5\u770B\u5F53\u524D\u53EF\u7528\u6A21\u578B\u5217\u8868\uFF1B\u4F18\u5148\u8BFB\u53D6 ~/.codex/models_cache.json\uFF0C--refresh \u53EF\u624B\u52A8\u91CD\u8BFB
21
+ status \u67E5\u770B\u5F53\u524D demo \u5F53\u524D\u6FC0\u6D3B\u8D26\u53F7\u3001\u8D26\u53F7\u6570\u91CF\u548C\u8FC7\u671F\u65F6\u95F4
18
22
  ask \u7528\u4FDD\u5B58\u7684 token \u8C03\u771F\u5B9E Codex Responses API
23
+ \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
24
+ \u5982\u54CD\u5E94\u91CC\u542B image_b64 / partial_image_b64\uFF0C\u53EF\u7528 --write-artifacts-dir \u76F4\u63A5\u5199\u6210\u56FE\u7247\u6587\u4EF6
25
+ start \u542F\u52A8\u672C\u5730 HTTP \u7F51\u5173\u5E76\u81EA\u52A8\u6253\u5F00\u7BA1\u7406\u9875\u9762
19
26
  serve \u542F\u52A8\u672C\u5730 HTTP \u7F51\u5173
20
27
  clear \u6E05\u7A7A demo \u7684\u672C\u5730\u72B6\u6001
21
28
  `);
@@ -23,4 +30,3 @@ function printHelp() {
23
30
  export {
24
31
  printHelp
25
32
  };
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