billion-context 0.1.19 → 0.1.20

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 CHANGED
@@ -40,54 +40,187 @@ npm install -g billion-context
40
40
 
41
41
  This installs the `bili` command (`bili-proxy` is kept as an alias).
42
42
 
43
- ## Usage
43
+ ## Quickstart
44
44
 
45
- ### Start the proxy
45
+ Three steps: **start the proxy → edit the config file → point your client at it**.
46
+ Compression is injected automatically — you only configure routing, never
47
+ compression itself.
48
+
49
+ ### Step 1 — Start the proxy
46
50
 
47
51
  ```bash
48
52
  bili
49
53
  ```
50
54
 
51
- That's it. The proxy reads its config from `~/.config/billion-context/billion-context.json` (XDG) and listens on `127.0.0.1:8787`. If no config file exists yet, it uses sensible defaults and logs where it expects the file.
55
+ It listens on `http://127.0.0.1:8787`. Keep this terminal open (or run in the
56
+ background; see [Running the proxy](#running-the-proxy)).
52
57
 
53
- ### Quick overrides (flags)
58
+ On first run `bili` **auto-creates an empty config file** and tells you where:
59
+ so you don't have to invent the schema from scratch:
54
60
 
55
- ```bash
56
- bili --port 9000 # change listen port
57
- bili --host 0.0.0.0 # listen on all interfaces
58
- bili --debug # verbose logging (also: set "debug": true in config)
59
- bili --passthrough # forward without compression (smoke-test mode)
60
- bili --config ~/my-bili.json # use a different config file
61
+ ```
62
+ [acp-config] created empty config at ~/.config/billion-context/billion-context.json — add your providers (see README Quickstart), then restart
61
63
  ```
62
64
 
63
- Flags override the config file and env vars. `bili --help` lists them all.
65
+ ### How routing works
64
66
 
65
- ### Point your agent at the proxy
67
+ The proxy routes by a **provider name in the URL path** — the first path
68
+ segment after the host. It strips the name and forwards the rest to that
69
+ provider. Everything after the name is passed through untouched:
66
70
 
67
- The proxy routes by a **provider name in the URL path**. Set your agent's base URL to `http://localhost:8787/<provider>/...` and the proxy forwards to that provider (see [Configuration](#configuration) for how providers are declared).
71
+ ```
72
+ client baseURL: http://localhost:8787/zhipu/api/coding/paas/v4
73
+ └──────────┬──────────┘└────────┬────────┘
74
+ proxy host remaining path
75
+ + provider name (forwarded as-is)
76
+ ```
68
77
 
69
- #### Claude Code (Anthropic)
78
+ This is why Step 2 has you declare named providers, and Step 3 has you put that
79
+ same name at the start of the client's base URL — it's how the proxy knows where
80
+ to send each request. In the config below, `zhipu` corresponds to:
81
+ ```json
82
+ "zhipu": {
83
+ "url": "https://open.bigmodel.cn",
84
+ "models": {
85
+ "glm-5.2": { "context": 1000000, "output": 131072 }
86
+ }
87
+ ```
88
+
89
+ ### Step 2 — Edit the config file
90
+
91
+ Open the file from Step 1 (`~/.config/billion-context/billion-context.json`)
92
+ and edit the `providers` block to match what you pay for. Each entry is a
93
+ **name → URL** mapping; the name is what you'll put in the client's base URL in
94
+ Step 3.
95
+
96
+ ```json
97
+ {
98
+ "providers": {
99
+ "zhipu": {
100
+ "url": "https://open.bigmodel.cn",
101
+ "models": {
102
+ "glm-5.2": { "context": 1000000, "output": 131072 }
103
+ }
104
+ },
105
+ "anthropic": "https://api.anthropic.com"
106
+ }
107
+ }
108
+ ```
109
+
110
+ - Delete providers you don't use.
111
+ - Add others (e.g. `"deepseek": "https://api.deepseek.com"`).
112
+ - The API key is **not** here — it lives in the client; the proxy passes it
113
+ through untouched.
114
+
115
+ After saving, **restart `bili`**. The startup banner lists your routes:
70
116
 
71
- ```bash
72
- export ANTHROPIC_BASE_URL=http://localhost:8787/anthropic
73
- export ANTHROPIC_API_KEY=sk-ant-... # real key — passed through as-is
74
- claude
75
117
  ```
118
+ acp-proxy listening on http://127.0.0.1:8787 — routes: anthropic=https://api.anthropic.com, zhipu=https://open.bigmodel.cn
119
+ ```
120
+
121
+ That confirms the proxy picked up your config. (Full schema — per-model context
122
+ windows, optional fields — is in [Configuration](#configuration).)
123
+
124
+ ### Step 3 — Point your client at the proxy
125
+
126
+ Edit the client's own config file so it sends requests to
127
+ `http://localhost:8787/<provider>/...` (the provider name from Step 2 as the
128
+ first path segment). Put your **real** API key in the client's config too —
129
+ the proxy passes it through untouched.
130
+
131
+ #### Pi (billion-context-pi)
132
+
133
+ Open `~/.pi/agent/models.json` and change your existing provider's **`baseUrl` line** to point at the proxy — leave every other field alone:
134
+
135
+ ```jsonc
136
+ // before:
137
+ "baseUrl": "https://open.bigmodel.cn/api/coding/paas/v4",
138
+ // after (swap the host for the proxy + the provider name you picked):
139
+ "baseUrl": "http://localhost:8787/zhipu/api/coding/paas/v4",
140
+ ```
141
+
142
+ `http://localhost:8787` is the proxy, `zhipu` is the name from Step 2, and the remaining path `/api/coding/paas/v4` is forwarded as-is to Zhipu. `apiKey`, `api`, and `models` stay unchanged.
143
+
144
+ | `api` value | `baseUrl` should point at |
145
+ |---|---|
146
+ | `openai-completions` | an OpenAI-compatible endpoint (GLM/DeepSeek/OpenAI) → `…/zhipu/...` |
147
+ | `anthropic-messages` | an Anthropic-compatible endpoint → `…/anthropic` |
76
148
 
77
- #### Codex / any OpenAI-compatible agent (zhipu / openai / deepseek)
149
+ > If you use the `billion-context-pi` extension, run Pi in an isolated agent
150
+ dir (`PI_CODING_AGENT_DIR=…`) so the client-side extension doesn't double-
151
+ compress alongside the proxy. The `bili-test-pi` helper does this for you.
152
+
153
+ #### OpenCode
154
+
155
+ Open `~/.config/opencode/opencode.json` and change your existing provider's **`baseURL` line** to point at the proxy:
156
+
157
+ ```jsonc
158
+ // before:
159
+ "baseURL": "https://open.bigmodel.cn/api/coding/paas/v4"
160
+ // after:
161
+ "baseURL": "http://localhost:8787/zhipu/api/coding/paas/v4"
162
+ ```
163
+
164
+ Everything else (`apiKey`, `models`) stays unchanged. For an Anthropic provider, change `baseURL` to `http://localhost:8787/anthropic`.
165
+
166
+ #### Codex
167
+
168
+ Open `~/.codex/config.toml` and change your existing provider's **`base_url` line** to point at the proxy:
169
+
170
+ ```toml
171
+ # before:
172
+ base_url = "https://open.bigmodel.cn/api/coding/paas/v4"
173
+ # after:
174
+ base_url = "http://localhost:8787/zhipu/api/coding/paas/v4"
175
+ ```
176
+
177
+ Everything else (`name`, `wire_api`, `env_key`) stays unchanged.
178
+
179
+ > Codex's Responses API needs an upstream that speaks the Responses protocol.
180
+ > Most regional OpenAI-compatible endpoints only speak `/chat/completions`; if
181
+ > yours 404s on `/responses`, use a relay that speaks Responses, or the
182
+ > official OpenAI API.
183
+
184
+ #### Other clients (Cursor / Aider / Continue …)
185
+
186
+ Not yet supported. The proxy currently speaks the Anthropic, OpenAI
187
+ chat-completions, and OpenAI Responses protocols — if your client uses a
188
+ different protocol or a non-standard auth header, it won't work yet.
189
+
190
+ ### Verify
191
+
192
+ With the proxy running and your config saved, check it answers and that your
193
+ first real request shows compression activity in the log:
78
194
 
79
195
  ```bash
80
- export OPENAI_BASE_URL=http://localhost:8787/zhipu/api/coding/paas/v4
81
- export OPENAI_API_KEY=<your real glm key> # passed through as-is
82
- codex
196
+ # Health check (proxy up + where it forwards)
197
+ curl -s http://localhost:8787/__acp/health
198
+ # → {"ok":true,"upstream":"https://api.anthropic.com"}
199
+
200
+ # Live session stats (after a real request)
201
+ curl -s http://localhost:8787/__acp/stats
83
202
  ```
84
203
 
85
- The `/zhipu/...` prefix tells the proxy to route to the `zhipu` provider; the
86
- remaining path is preserved.
204
+ Then send one message from your client and watch the log
205
+ (`~/.local/state/billion-context/bili.log`, also printed to stderr). You
206
+ should see a `processTurn` line per request, and once the conversation grows,
207
+ `[acp-usage] round N input=X cached=Y (cache hit Z%)` + a `compress` event.
87
208
 
88
- #### Cursor / Aider / others
209
+ ## Running the proxy
89
210
 
90
- Set the base URL to `http://localhost:8787/<provider>` in the agent's settings.
211
+ ### Flags
212
+
213
+ ```bash
214
+ bili --port 9000 # change listen port
215
+ bili --host 0.0.0.0 # listen on all interfaces (see host note below)
216
+ bili --debug # verbose logging (also: set "debug": true in config)
217
+ bili --passthrough # forward without compression (smoke-test mode)
218
+ bili --config ~/my-bili.json # use a different config file
219
+ bili update # check & install a newer version now (bypasses throttle)
220
+ bili --no-auto-update # disable self-update for this run
221
+ ```
222
+
223
+ Flags override env vars and the config file. `bili --help` lists them all.
91
224
 
92
225
  ### Debugging
93
226
 
@@ -108,8 +241,6 @@ All logs are **tee'd to a file by default**: `~/.local/state/billion-context/bil
108
241
  shows them in the terminal.
109
242
 
110
243
  ```bash
111
- bili start # logs → ~/.local/state/billion-context/bili.log + terminal
112
- bili update # (see below)
113
244
  # Config: "logFile": "/custom/path.log"
114
245
  # Env: ACP_LOG_FILE=/custom/path.log (or ACP_LOG_FILE=off to disable the file)
115
246
  ```
@@ -124,11 +255,6 @@ The proxy checks npm for a newer version on startup and every 3 minutes. When a
124
255
  newer version is found it installs it globally (`npm install -g`) and logs a
125
256
  notice — **restart `bili` to pick up the new version**.
126
257
 
127
- ```bash
128
- bili update # check & install now (manual, bypasses 3min throttle)
129
- bili --no-auto-update # disable self-update for this run
130
- ```
131
-
132
258
  Disable permanently via config (`"autoUpdate": false`) or env
133
259
  (`ACP_AUTO_UPDATE=0`).
134
260
 
@@ -259,43 +385,15 @@ built-in model table, then to `modelContextLimit`.
259
385
  **API keys are never stored in the proxy** — whatever key the agent sends is
260
386
  passed through untouched to the upstream.
261
387
 
262
- ### Routing
263
-
264
- Point any agent at the proxy using a provider name as a path segment. The
265
- proxy strips the name and forwards to that provider's root URL.
266
-
267
- ```
268
- agent baseURL: http://localhost:8787/zhipu/api/coding/paas/v4
269
- └──────────┬──────────┘└────────┬────────┘
270
- proxy host remaining path
271
- + provider name (forwarded as-is)
272
- ```
273
-
274
- #### Claude Code (Anthropic)
275
-
276
- ```bash
277
- export ANTHROPIC_BASE_URL=http://localhost:8787/anthropic
278
- export ANTHROPIC_API_KEY=sk-ant-... # real key — passed through as-is
279
- claude
280
- ```
281
-
282
- #### Codex / any OpenAI-compatible agent (zhipu / openai / deepseek)
283
-
284
- ```bash
285
- export OPENAI_BASE_URL=http://localhost:8787/zhipu/api/coding/paas/v4
286
- export OPENAI_API_KEY=<your real glm key> # passed through as-is
287
- codex
288
- ```
289
-
290
- The `/zhipu/...` prefix tells the proxy to route to the `zhipu` provider; the
291
- remaining `/api/coding/paas/v4/...` path is preserved.
292
-
293
- ### Notes on provider names
388
+ ### Provider name rules
294
389
 
295
390
  - Must start with a letter, contain only letters/digits/`-`/`_`.
296
391
  - Reserved words (`v1`, `chat`, `completions`, `messages`, `models`, `api`)
297
392
  are rejected to avoid colliding with real API path segments.
298
393
  - The provider name can appear anywhere in the path; the longest match wins.
394
+ - With **no providers** declared (e.g. you emptied the `providers` block),
395
+ every request is forwarded to the default `upstream` with its full path —
396
+ an edge case, not the normal flow.
299
397
 
300
398
  ## How sessions work
301
399
 
package/README.zh-CN.md CHANGED
@@ -40,53 +40,176 @@ npm install -g billion-context
40
40
 
41
41
  这会安装 `bili` 命令(`bili-proxy` 保留为别名)。
42
42
 
43
- ## 用法
43
+ ## 快速上手
44
44
 
45
- ### 启动代理
45
+ 三步:**启动代理 → 编辑配置文件 → 把客户端指向它**。
46
+ 压缩是自动注入的 —— 你只需配置路由,无需配置压缩本身。
47
+
48
+ ### 第 1 步 —— 启动代理
46
49
 
47
50
  ```bash
48
51
  bili
49
52
  ```
50
53
 
51
- 就这么简单。代理从 `~/.config/billion-context/billion-context.json`(XDG)读取配置,监听 `127.0.0.1:8787`。如果配置文件还不存在,会用合理默认值,并打印期望的文件位置。
54
+ 它监听 `http://127.0.0.1:8787`。保持这个终端开着(或后台运行,见[运行代理](#运行代理))。
52
55
 
53
- ### 快速覆盖(命令行参数)
56
+ 首次运行 `bili` 会**自动创建一份空的配置文件**,并告诉你路径:
54
57
 
55
- ```bash
56
- bili --port 9000 # 改监听端口
57
- bili --host 0.0.0.0 # 监听所有网卡
58
- bili --debug # 详细日志(也可在配置里设 "debug": true)
59
- bili --passthrough # 不压缩直接转发(冒烟测试模式)
60
- bili --config ~/my-bili.json # 用别的配置文件
58
+ ```
59
+ [acp-config] created empty config at ~/.config/billion-context/billion-context.json — add your providers (see README Quickstart), then restart
61
60
  ```
62
61
 
63
- 参数优先级高于配置文件和环境变量。`bili --help` 列出全部。
62
+ ### 路由怎么工作
64
63
 
65
- ### 把你的助手指向代理
64
+ 代理按 **URL 路径里的 provider 名**路由 —— host 后面的第一段路径。它会
65
+ 剥离该名字,把剩余部分转发给那个 provider。名字之后的整段路径原样透传:
66
66
 
67
- 代理按 **URL 路径里的 provider 名**路由。把助手的 base URL 设为 `http://localhost:8787/<provider>/...`,代理就转发到该 provider(如何在配置里声明 provider 见[配置](#配置))。
67
+ ```
68
+ 客户端 baseURL: http://localhost:8787/zhipu/api/coding/paas/v4
69
+ └──────────┬──────────┘└────────┬────────┘
70
+ 代理 host 剩余路径
71
+ + provider 名 (原样转发)
72
+ ```
68
73
 
69
- #### Claude Code(Anthropic)
74
+ 这就是为什么第 2 步让你声明带名字的 provider、第 3 步又让你把同样的名字
75
+ 放在客户端 base URL 的开头 —— 代理凭这个知道每条请求发到哪。
76
+ 稍后在配置文件中,`zhipu` 对应的是:
77
+ ```json
78
+ "zhipu": {
79
+ "url": "https://open.bigmodel.cn",
80
+ "models": {
81
+ "glm-5.2": { "context": 1000000, "output": 131072 }
82
+ }
83
+ ```
70
84
 
71
- ```bash
72
- export ANTHROPIC_BASE_URL=http://localhost:8787/anthropic
73
- export ANTHROPIC_API_KEY=sk-ant-... # 真实 key —— 原样透传
74
- claude
85
+
86
+ ### 第 2 步 —— 编辑配置文件
87
+
88
+ 打开第 1 步创建的文件(`~/.config/billion-context/billion-context.json`),
89
+ 编辑 `providers` 块,填入你付费使用的 provider。每个条目是一个
90
+ **名字 → URL** 映射;这个名字就是你在第 3 步里写进客户端 base URL 的东西。
91
+
92
+ ```json
93
+ {
94
+ "providers": {
95
+ "zhipu": {
96
+ "url": "https://open.bigmodel.cn",
97
+ "models": {
98
+ "glm-5.2": { "context": 1000000, "output": 131072 }
99
+ }
100
+ },
101
+ "anthropic": "https://api.anthropic.com",
102
+ }
103
+ }
75
104
  ```
76
105
 
77
- #### Codex / 任意 OpenAI 兼容助手(智谱 / openai / deepseek)
106
+ - 删掉你不用的 provider。
107
+ - 添加其他的(例如 `"deepseek": "https://api.deepseek.com"`)。
108
+ - API key **不**写在这里 —— key 在客户端那边,代理原样透传。
109
+
110
+ 保存后**重启 `bili`**。启动行列出你的路由:
111
+
112
+ ```
113
+ acp-proxy listening on http://127.0.0.1:8787 — routes: anthropic=https://api.anthropic.com, zhipu=https://open.bigmodel.cn
114
+ ```
115
+
116
+ 这证明代理读到了你的配置。(完整 schema —— 按模型的 context 窗口、可选字段 —— 见[配置](#配置)。)
117
+
118
+ ### 第 3 步 —— 把客户端指向代理
119
+
120
+ 编辑客户端自己的配置文件,让它把请求发到
121
+ `http://localhost:8787/<provider>/...`(第 2 步声明的 provider 名作为路径
122
+ 第一段)。把你的**真实** API key 也填进客户端配置 —— 代理原样透传。
123
+
124
+ #### Pi(billion-context-pi)
125
+
126
+ 打开 `~/.pi/agent/models.json`,把你现有 provider 的 **`baseUrl` 这一行**改成指向代理,其他字段都不用动:
127
+
128
+ ```jsonc
129
+ // 改之前:
130
+ "baseUrl": "https://open.bigmodel.cn/api/coding/paas/v4",
131
+ // 改之后(把 host 换成代理 + 你起的 provider 名):
132
+ "baseUrl": "http://localhost:8787/zhipu/api/coding/paas/v4",
133
+ ```
134
+
135
+ `http://localhost:8787` 是代理,`zhipu` 是第 2 步起的名字,剩余路径 `/api/coding/paas/v4` 原样转发到智谱。`apiKey`、`api`、`models` 都不用改。
136
+
137
+ | `api` 值 | `baseUrl` 应指向 |
138
+ |---|---|
139
+ | `openai-completions` | OpenAI 兼容端点(GLM/DeepSeek/OpenAI)→ `…/zhipu/...` |
140
+ | `anthropic-messages` | Anthropic 兼容端点 → `…/anthropic` |
141
+
142
+ > 如果你装了 `billion-context-pi` 扩展,用隔离的 agent 目录跑 Pi
143
+ > (`PI_CODING_AGENT_DIR=…`),免得客户端扩展和 proxy 双重压缩。
144
+ > `bili-test-pi` 脚本帮你做好了这层隔离。
145
+
146
+ #### OpenCode
147
+
148
+ 打开 `~/.config/opencode/opencode.json`,把你现有 provider 的 **`baseURL` 这一行**改成指向代理:
149
+
150
+ ```jsonc
151
+ // 改之前:
152
+ "baseURL": "https://open.bigmodel.cn/api/coding/paas/v4"
153
+ // 改之后:
154
+ "baseURL": "http://localhost:8787/zhipu/api/coding/paas/v4"
155
+ ```
156
+
157
+ 其他字段(`apiKey`、`models`)都不用改。如果要走 Anthropic provider,把 `baseURL` 改为 `http://localhost:8787/anthropic`。
158
+
159
+ #### Codex
160
+
161
+ 打开 `~/.codex/config.toml`,把现有 provider 的 **`base_url` 这一行**改成指向代理:
162
+
163
+ ```toml
164
+ # 改之前:
165
+ base_url = "https://open.bigmodel.cn/api/coding/paas/v4"
166
+ # 改之后:
167
+ base_url = "http://localhost:8787/zhipu/api/coding/paas/v4"
168
+ ```
169
+
170
+ 其他字段(`name`、`wire_api`、`env_key`)都不用改。
171
+
172
+ > Codex 的 Responses API 需要上游说 Responses 协议。多数区域性 OpenAI
173
+ > 兼容端点只说 `/chat/completions`;如果你的端点在 `/responses` 上 404,
174
+ > 用一个说 Responses 的中转,或用官方 OpenAI API。
175
+
176
+ #### 其他客户端(Cursor / Aider / Continue …)
177
+
178
+ 暂不支持。代理目前说 Anthropic、OpenAI chat-completions、OpenAI Responses
179
+ 三种协议 —— 如果你的客户端用别的协议或非标准 auth header,还用不了。
180
+
181
+ ### 验证
182
+
183
+ 代理跑着、配置保存了之后,确认它能应答,并且第一个真实请求在日志里显示压缩活动:
78
184
 
79
185
  ```bash
80
- export OPENAI_BASE_URL=http://localhost:8787/zhipu/api/coding/paas/v4
81
- export OPENAI_API_KEY=<你的真实智谱 key> # 原样透传
82
- codex
186
+ # 健康检查(代理是否在跑 + 转发到哪)
187
+ curl -s http://localhost:8787/__acp/health
188
+ # → {"ok":true,"upstream":"https://api.anthropic.com"}
189
+
190
+ # 实时会话统计(发过真实请求后)
191
+ curl -s http://localhost:8787/__acp/stats
83
192
  ```
84
193
 
85
- `/zhipu/...` 前缀告诉代理路由到 `zhipu` provider;剩余路径保留不变。
194
+ 然后从助手发一条消息,观察日志(`~/.local/state/billion-context/bili.log`,
195
+ 同时也打到 stderr)。每个请求应该看到一行 `processTurn`,等对话变长后
196
+ 会出现 `[acp-usage] round N input=X cached=Y (cache hit Z%)` + `compress` 事件。
86
197
 
87
- #### Cursor / Aider / 其他
198
+ ## 运行代理
88
199
 
89
- 在助手设置里把 base URL 设为 `http://localhost:8787/<provider>`。
200
+ ### 命令行参数
201
+
202
+ ```bash
203
+ bili --port 9000 # 改监听端口
204
+ bili --host 0.0.0.0 # 监听所有网卡(见下面的 host 说明)
205
+ bili --debug # 详细日志(也可在配置里设 "debug": true)
206
+ bili --passthrough # 不压缩直接转发(冒烟测试模式)
207
+ bili --config ~/my-bili.json # 用别的配置文件
208
+ bili update # 立即检查并安装新版本(跳过节流)
209
+ bili --no-auto-update # 本次启动禁用自动更新
210
+ ```
211
+
212
+ 参数优先级高于环境变量和配置文件。`bili --help` 列出全部。
90
213
 
91
214
  ### 调试
92
215
 
@@ -100,11 +223,10 @@ codex
100
223
 
101
224
  ### 日志文件
102
225
 
103
- 所有日志**默认同时写入文件**:`~/.local/state/billion-context/bili.log`(XDG state 目录)。同时仍打印到 stderr,所以前台运行 `bili start` 时终端也能看到。
226
+ 所有日志**默认同时写入文件**:`~/.local/state/billion-context/bili.log`
227
+ (XDG state 目录)。同时仍打印到 stderr,所以前台运行 `bili start` 时终端也能看到。
104
228
 
105
229
  ```bash
106
- bili start # 日志 → ~/.local/state/billion-context/bili.log + 终端
107
- bili update # (见下文)
108
230
  # 配置: "logFile": "/custom/path.log"
109
231
  # 环境变量: ACP_LOG_FILE=/custom/path.log (或 ACP_LOG_FILE=off 关闭文件,只保留 stderr)
110
232
  ```
@@ -115,11 +237,6 @@ bili update # (见下文)
115
237
 
116
238
  代理启动时和每 3 分钟检查 npm 是否有新版本。发现新版本就全局安装(`npm install -g`)并打印通知 —— **重启 `bili` 才能生效**。
117
239
 
118
- ```bash
119
- bili update # 立即检查并安装(手动,跳过 3 分钟节流)
120
- bili --no-auto-update # 本次启动禁用自动更新
121
- ```
122
-
123
240
  永久禁用:配置(`"autoUpdate": false`)或环境变量(`ACP_AUTO_UPDATE=0`)。
124
241
 
125
242
  ## 配置
@@ -232,40 +349,13 @@ bili --no-auto-update # 本次启动禁用自动更新
232
349
 
233
350
  **API key 永远不存进代理** —— 助手发什么 key,原样透传给上游。
234
351
 
235
- ### 路由
236
-
237
- 用 provider 名作为路径段把任意助手指向代理。代理剥离该名字并转发到该 provider 的根 URL。
238
-
239
- ```
240
- 助手 baseURL: http://localhost:8787/zhipu/api/coding/paas/v4
241
- └──────────┬──────────┘└────────┬────────┘
242
- 代理 host 剩余路径
243
- + provider 名 (原样转发)
244
- ```
245
-
246
- #### Claude Code(Anthropic)
247
-
248
- ```bash
249
- export ANTHROPIC_BASE_URL=http://localhost:8787/anthropic
250
- export ANTHROPIC_API_KEY=sk-ant-... # 真实 key —— 原样透传
251
- claude
252
- ```
253
-
254
- #### Codex / 任意 OpenAI 兼容助手(智谱 / openai / deepseek)
255
-
256
- ```bash
257
- export OPENAI_BASE_URL=http://localhost:8787/zhipu/api/coding/paas/v4
258
- export OPENAI_API_KEY=<你的真实智谱 key> # 原样透传
259
- codex
260
- ```
261
-
262
- `/zhipu/...` 前缀告诉代理路由到 `zhipu` provider;剩余 `/api/coding/paas/v4/...` 路径保留不变。
263
-
264
- ### Provider 名注意事项
352
+ ### Provider 名规则
265
353
 
266
354
  - 必须以字母开头,只含字母/数字/`-`/`_`。
267
355
  - 保留字(`v1`、`chat`、`completions`、`messages`、`models`、`api`)被拒绝,以免与真实 API 路径段冲突。
268
356
  - provider 名可出现在路径任意位置;最长匹配优先。
357
+ - **未声明任何 providers**(比如你清空了 `providers` 块)时,每个请求按完整
358
+ 原始路径转发到默认 `upstream` —— 边缘场景,非正常流程。
269
359
 
270
360
  ## 会话机制
271
361
 
package/dist/index.js CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  // src/config.ts
4
4
  import { defaultConfig } from "acp-kernel";
5
- import { readFileSync } from "fs";
5
+ import { readFileSync, existsSync, mkdirSync as mkdirSync2, writeFileSync } from "fs";
6
+ import { dirname } from "path";
6
7
 
7
8
  // src/paths.ts
8
9
  import { homedir } from "os";
@@ -194,6 +195,22 @@ function loadConfigFile() {
194
195
  }
195
196
  return {};
196
197
  }
198
+ var TEMPLATE_CONFIG = `{
199
+ "providers": {
200
+ }
201
+ }`;
202
+ function ensureConfigTemplate() {
203
+ const p = configFile();
204
+ if (existsSync(p)) return false;
205
+ try {
206
+ mkdirSync2(dirname(p), { recursive: true });
207
+ writeFileSync(p, TEMPLATE_CONFIG + "\n", "utf8");
208
+ log("info", `[acp-config] created empty config at ${p} \u2014 add your providers (see README Quickstart), then restart`);
209
+ return true;
210
+ } catch {
211
+ return false;
212
+ }
213
+ }
197
214
  function parseRouteEntry(v) {
198
215
  if (typeof v === "string" && v.length > 0) {
199
216
  return { url: v.replace(/\/$/, "") };
@@ -743,7 +760,7 @@ import { createInitialState as createInitialState2 } from "acp-kernel";
743
760
 
744
761
  // src/persist.ts
745
762
  import { promises as fs } from "fs";
746
- import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
763
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
747
764
  import { createHash as createHash2 } from "crypto";
748
765
  import * as path3 from "path";
749
766
  import { createInitialState } from "acp-kernel";
@@ -859,7 +876,7 @@ var SessionStore = class {
859
876
  const candidates = [this.filePath(id, meta?.protocol, meta?.upstreamOrigin)];
860
877
  if (meta?.protocol) candidates.push(this.filePath(id));
861
878
  for (const file of candidates) {
862
- if (!existsSync(file)) continue;
879
+ if (!existsSync2(file)) continue;
863
880
  try {
864
881
  const parsed = JSON.parse(readFileSync2(file, "utf8"));
865
882
  if (!isValidRecord(parsed) || parsed.id !== id) continue;
@@ -926,13 +943,13 @@ var SessionStore = class {
926
943
  const record = buildRecord(session);
927
944
  const file = this.filePath(session.id, session.protocol, session.upstreamOrigin);
928
945
  try {
929
- mkdirSync2(path3.dirname(file), { recursive: true });
946
+ mkdirSync3(path3.dirname(file), { recursive: true });
930
947
  } catch (e) {
931
948
  this.log("warn", `[persist] could not create session dir ${this.dir}: ${msg(e)}`);
932
949
  }
933
950
  const tmp = this.tempPath(session.id);
934
951
  try {
935
- writeFileSync(tmp, JSON.stringify(record), "utf8");
952
+ writeFileSync2(tmp, JSON.stringify(record), "utf8");
936
953
  renameSync2(tmp, file);
937
954
  return true;
938
955
  } catch (e) {
@@ -1608,8 +1625,8 @@ import {
1608
1625
  collectBlockContent as collectBlockContent2,
1609
1626
  deactivateBlock
1610
1627
  } from "acp-kernel";
1611
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
1612
- import { dirname as dirname2, join as join2 } from "path";
1628
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
1629
+ import { dirname as dirname3, join as join2 } from "path";
1613
1630
  import { tmpdir } from "os";
1614
1631
  function resolveDecompress(args, ctx) {
1615
1632
  const rawBlockId = args.blockId;
@@ -1641,8 +1658,8 @@ function resolveDecompress(args, ctx) {
1641
1658
  const outPath = body.length > 1e4 ? join2(tmpdir(), `acp-decompress-${safeBlockId}-${Date.now()}.txt`) : null;
1642
1659
  if (outPath) {
1643
1660
  try {
1644
- mkdirSync3(dirname2(outPath), { recursive: true });
1645
- writeFileSync2(outPath, body, "utf8");
1661
+ mkdirSync4(dirname3(outPath), { recursive: true });
1662
+ writeFileSync3(outPath, body, "utf8");
1646
1663
  return `${header}
1647
1664
  Content (${body.length} chars) written to: ${outPath}
1648
1665
  Use the read tool to access it.`;
@@ -3161,10 +3178,10 @@ async function pipeThrough(stream2, res) {
3161
3178
  }
3162
3179
  }
3163
3180
  async function dumpStreamToFile(stream2, dir, name) {
3164
- const { mkdirSync: mkdirSync4, createWriteStream: createWriteStream2 } = await import("fs");
3181
+ const { mkdirSync: mkdirSync5, createWriteStream: createWriteStream2 } = await import("fs");
3165
3182
  const { join: join3 } = await import("path");
3166
3183
  try {
3167
- mkdirSync4(dir, { recursive: true });
3184
+ mkdirSync5(dir, { recursive: true });
3168
3185
  const ws = createWriteStream2(join3(dir, name));
3169
3186
  const reader = stream2.getReader();
3170
3187
  try {
@@ -3641,6 +3658,7 @@ async function main() {
3641
3658
  for (const [k, v] of Object.entries(overrides)) {
3642
3659
  if (v !== void 0) process.env[k] = v;
3643
3660
  }
3661
+ ensureConfigTemplate();
3644
3662
  const opts = loadOptions();
3645
3663
  await startServer(opts);
3646
3664
  if (opts.autoUpdate) {