glmproxy 2.5.1 → 2.6.0
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 +243 -270
- package/anthropic.js +734 -734
- package/bin/cli.js +406 -406
- package/lib/core.js +1454 -1434
- package/lib/prompts.js +113 -113
- package/openai.js +425 -425
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# GLM Proxy
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<b>
|
|
4
|
+
<b>Use your AutoClaw GLM models in any tool that speaks the OpenAI or Anthropic API</b><br>
|
|
5
|
+
<sub>Claude Code, Cursor, Continue, OpenCode, LiteLLM, raw SDKs. Point them here instead.</sub>
|
|
5
6
|
</p>
|
|
6
7
|
|
|
7
8
|
<p align="center">
|
|
@@ -13,219 +14,273 @@
|
|
|
13
14
|
<img src="https://github.com/eequaled/GLM_proxy/actions/workflows/ci.yml/badge.svg" alt="CI">
|
|
14
15
|
</p>
|
|
15
16
|
|
|
16
|
-
>
|
|
17
|
+
<p align="center"><sub>v2.6.0. One CLI, two API formats, zero dependencies.</sub></p>
|
|
17
18
|
|
|
18
19
|
---
|
|
19
20
|
|
|
20
|
-
##
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm i -g glmproxy
|
|
25
|
+
glmproxy
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
That's it, an interactive menu walks you through format, port, and key. Prefer flags?
|
|
21
29
|
|
|
22
|
-
|
|
30
|
+
```bash
|
|
31
|
+
glmproxy --anthropic --port 3001 --key mykey
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then point any OpenAI-compatible tool at `http://127.0.0.1:18791/v1`, or any Anthropic-compatible tool at `http://127.0.0.1:18792`. Default key is `mewmew` (see [Integrations](#integrations) below for exact per-tool setup).
|
|
23
35
|
|
|
24
|
-
**
|
|
36
|
+
**You need:** [AutoClaw](https://autoclaw.z.ai) installed, running, and logged in (Windows/macOS), plus Node.js 18+. The proxy reads auth straight from AutoClaw's local token file, so there's no manual token setup and no API keys to copy.
|
|
25
37
|
|
|
26
38
|
---
|
|
27
39
|
|
|
28
|
-
|
|
40
|
+
## Integrations
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
### Claude Code CLI
|
|
43
|
+
|
|
44
|
+
Add to `~/.claude/settings.json`:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"env": {
|
|
49
|
+
"ANTHROPIC_BASE_URL": "http://localhost:18792",
|
|
50
|
+
"ANTHROPIC_AUTH_TOKEN": "mewmew"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### OpenCode
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"provider": {
|
|
60
|
+
"autoclaw": {
|
|
61
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
62
|
+
"name": "AutoClaw",
|
|
63
|
+
"options": {
|
|
64
|
+
"baseURL": "http://localhost:18791/v1",
|
|
65
|
+
"apiKey": "mewmew"
|
|
66
|
+
},
|
|
67
|
+
"models": {
|
|
68
|
+
"zai_auto": { "name": "AutoClaw Auto" },
|
|
69
|
+
"zaicoding_glm-5.3": { "name": "AutoClaw GLM-5.3" },
|
|
70
|
+
"zai_glm-5-turbo": { "name": "AutoClaw GLM-5 Turbo" },
|
|
71
|
+
"tdpsk_deepseek-v4-flash-202605": { "name": "AutoClaw Deepseek-V4-Flash" },
|
|
72
|
+
"tdpsk_deepseek-v4-pro-202606": { "name": "AutoClaw DeepSeek-V4-Pro" }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Cursor / Continue / anything OpenAI-compatible
|
|
80
|
+
|
|
81
|
+
Point it at `http://localhost:18791/v1` with API key `mewmew`. Harnesses that probe for available models pick up the live list from `/v1/models` automatically.
|
|
82
|
+
|
|
83
|
+
Or add it as a custom model directly in the UI:
|
|
84
|
+
- **API Format**: OpenAI Chat Completions
|
|
85
|
+
- **URL**: `http://localhost:18791/v1`
|
|
86
|
+
- **Model ID**: `zai_auto` (or any model from the table below)
|
|
87
|
+
- **API Key**: `mewmew`
|
|
88
|
+
|
|
89
|
+
### Python
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from openai import OpenAI
|
|
93
|
+
|
|
94
|
+
client = OpenAI(base_url="http://localhost:18791/v1", api_key="mewmew")
|
|
95
|
+
|
|
96
|
+
with client.chat.completions.stream(
|
|
97
|
+
model="zai_auto",
|
|
98
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
99
|
+
) as stream:
|
|
100
|
+
for text in stream.text_stream:
|
|
101
|
+
print(text, end="", flush=True)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### JavaScript
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
import OpenAI from "openai";
|
|
108
|
+
|
|
109
|
+
const client = new OpenAI({
|
|
110
|
+
baseURL: "http://localhost:18791/v1",
|
|
111
|
+
apiKey: "mewmew",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const stream = await client.chat.completions.create({
|
|
115
|
+
model: "zai_auto",
|
|
116
|
+
messages: [{ role: "user", content: "Hello!" }],
|
|
117
|
+
stream: true,
|
|
118
|
+
});
|
|
34
119
|
|
|
35
|
-
|
|
120
|
+
for await (const chunk of stream) {
|
|
121
|
+
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Why this exists
|
|
36
128
|
|
|
37
|
-
|
|
129
|
+
AutoClaw gives you Zhipu's GLM models (GLM-5.3, GLM-5-Turbo, GLM-5.3-Flash, plus DeepSeek), but locks them inside its own desktop app. This proxy speaks OpenAI and Anthropic API dialects on one side and AutoClaw's native protocol on the other, so any tool built for those APIs can drive AutoClaw's models. The model list is pulled live from AutoClaw's runtime config, so anything AutoClaw adds or removes shows up without a proxy restart.
|
|
38
130
|
|
|
39
|
-
|
|
131
|
+
## How it works
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
Your App GLM Proxy AutoClaw Backend
|
|
135
|
+
(OpenAI SDK) ───▶ 127.0.0.1:18791 (OpenAI format) ───▶ autoglm-api.autoglm.ai (cloud)
|
|
136
|
+
127.0.0.1:18792 (Anthropic format)
|
|
137
|
+
│ cloud fails
|
|
138
|
+
▼
|
|
139
|
+
AutoClaw desktop agent
|
|
140
|
+
127.0.0.1:18789 (local WebSocket)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
AutoClaw handles authentication automatically. When the cloud path fails, requests fall back to AutoClaw's own desktop agent over a local WebSocket. See [Local gateway fallback](#local-gateway-fallback) for details.
|
|
40
144
|
|
|
41
145
|
<p align="center">
|
|
42
|
-
<i>
|
|
146
|
+
<i>AutoClaw running as your background service, that's the whole "auth" story</i>
|
|
43
147
|
<br>
|
|
44
148
|
<img src="./screenshots/autoclaw-background.png" alt="AutoClaw running as background service" width="700">
|
|
45
149
|
</p>
|
|
46
150
|
|
|
47
|
-
### 2. Proxy in action — it works
|
|
48
|
-
|
|
49
|
-
Start the proxy and watch it handle requests from your tool of choice.
|
|
50
|
-
|
|
51
151
|
<p align="center">
|
|
52
|
-
<i>ignore claude code here</i>
|
|
152
|
+
<i>the proxy in action (ignore claude code here)</i>
|
|
53
153
|
<br>
|
|
54
154
|
<img src="./screenshots/image.png" alt="Proxy terminal showing successful operation" width="700">
|
|
55
155
|
</p>
|
|
56
156
|
|
|
57
|
-
##
|
|
157
|
+
## Current available models in autoclaw
|
|
58
158
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
159
|
+
| ID | Name | Context | Max Output | Notes |
|
|
160
|
+
|----|------|---------|------------|-------|
|
|
161
|
+
| `zai_auto` | Auto | 1M | 393K | Routes to AutoClaw's optimal model |
|
|
162
|
+
| `zaicoding_glm-5.3` | GLM-5.3 | 1M | 307K | Latest GLM coding model |
|
|
163
|
+
| `zai_glm-5-turbo` | GLM-5-Turbo | 200K | 131K | Zhipu AI GLM-5 Turbo |
|
|
164
|
+
| `zai_glm-5.3-flash` | GLM-5.3-Flash ("OX-alpha") | 1M | 131K | Now a regular catalog model, served straight through the cloud path |
|
|
165
|
+
| `tdpsk_deepseek-v4-flash-202605` | Deepseek-V4-Flash | 1M | 393K | Fast DeepSeek model |
|
|
166
|
+
| `tdpsk_deepseek-v4-pro-202606` | DeepSeek-V4-Pro | 1M | 393K | Deep reasoning model |
|
|
64
167
|
|
|
65
|
-
|
|
168
|
+
> GLM 5.3 flash new in the proxy!!!!! ox alpha the goat
|
|
66
169
|
|
|
67
|
-
The
|
|
170
|
+
The catalog is re-read from AutoClaw's `openclaw.runtime.json` on every `/v1/models` call, with a built-in fallback if that file isn't readable. Run `glmproxy --doctor` to inspect the current catalog after an AutoClaw update. Claude model names sent to the Anthropic proxy are mapped automatically:
|
|
68
171
|
|
|
69
|
-
|
|
172
|
+
| Claude model | Routes to |
|
|
173
|
+
|---|---|
|
|
174
|
+
| `claude-opus-*` | First available GLM-5.3 / GLM-5 model |
|
|
175
|
+
| `claude-sonnet-*` | `zai_auto` (or next available GLM-5 model) |
|
|
176
|
+
| `claude-haiku-*` | `zai_glm-5-turbo` (or DeepSeek / Auto fallback) |
|
|
70
177
|
|
|
71
|
-
|
|
72
|
-
- Node.js 18+
|
|
178
|
+
---
|
|
73
179
|
|
|
74
|
-
|
|
180
|
+
<details>
|
|
181
|
+
<summary><h2>CLI reference</h2></summary>
|
|
75
182
|
|
|
76
|
-
|
|
77
|
-
npm start
|
|
78
|
-
# or: node bin/cli.js
|
|
79
|
-
```
|
|
183
|
+
Running `glmproxy` with no flags on a real terminal opens an interactive menu: arrow keys to move, Enter to pick. Choose format, port, host, and auth key, or run **Model Doctor** / **Test Models** without starting a proxy. Ctrl+C quits cleanly and restores your terminal. Without a TTY (piped stdin, CI), the CLI skips the menu and starts the OpenAI format on port 18791 using your env vars or defaults.
|
|
80
184
|
|
|
81
|
-
|
|
185
|
+
| Format | Flag | Default port | Use with |
|
|
186
|
+
|--------|------|--------------|----------|
|
|
187
|
+
| OpenAI (`/v1/chat/completions`) | `--openai` (default) | `18791` | OpenCode, Cursor, Continue, LiteLLM, Python/JS SDKs |
|
|
188
|
+
| Anthropic (`/v1/messages`) | `--anthropic` | `18792` | Claude Code CLI, Anthropic SDK |
|
|
82
189
|
|
|
83
|
-
|
|
190
|
+
Config can also come from env vars. The CLI leaves existing ones alone:
|
|
84
191
|
|
|
85
192
|
```bash
|
|
86
|
-
|
|
193
|
+
PORT=3001 PROXY_KEY=mykey RATE_LIMIT=50 glmproxy
|
|
87
194
|
```
|
|
88
195
|
|
|
89
|
-
|
|
196
|
+
**Direct entry points**, if you'd rather skip the CLI entirely:
|
|
90
197
|
|
|
91
198
|
```bash
|
|
92
|
-
|
|
199
|
+
node openai.js # OpenAI format, port 18791
|
|
200
|
+
node anthropic.js # Anthropic format, port 18792
|
|
93
201
|
```
|
|
94
202
|
|
|
95
|
-
|
|
203
|
+
They read the same env vars and respect `HOST`, `PORT`, `PROXY_KEY`, `RATE_LIMIT`, etc.
|
|
96
204
|
|
|
97
|
-
|
|
205
|
+
**npm commands:**
|
|
98
206
|
|
|
99
207
|
| Command | What it does |
|
|
100
208
|
|---------|--------------|
|
|
101
209
|
| `npm start` | Launch the interactive CLI (`node bin/cli.js`) |
|
|
102
210
|
| `npm run anthropic` | Start the Anthropic proxy directly (`node anthropic.js`), bypassing the menu |
|
|
103
|
-
| `npm test` | Run the pen-test suite
|
|
211
|
+
| `npm test` | Run the pen-test suite, error-taxonomy tests, and the runtime-catalog refresh test |
|
|
104
212
|
|
|
105
|
-
|
|
213
|
+
Dev usage from a checkout: `npm start` or `node bin/cli.js`.
|
|
106
214
|
|
|
107
|
-
|
|
215
|
+
**Model doctor** scans AutoClaw's live model catalog with credit tiers and prints the Claude alias routing map:
|
|
108
216
|
|
|
109
217
|
```bash
|
|
110
|
-
|
|
111
|
-
node anthropic.js # Anthropic format, port 18792
|
|
218
|
+
glmproxy --doctor
|
|
112
219
|
```
|
|
113
220
|
|
|
114
|
-
|
|
221
|
+
Anthropic routing follows credit tiers: opus goes to High, sonnet to Medium, haiku to Low. UI display names can differ from API ids (e.g. the API's `zaicoding_glm-5.3` shows as "GLM-5.2" in AutoClaw's UI).
|
|
222
|
+
|
|
223
|
+
**Model health test** spawns a throwaway proxy and fires a minimal prompt at every catalog model:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
glmproxy --test-models
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
✔ working [cloud ok] (1.2s) → PONG
|
|
231
|
+
✔ working [cloud 402 → local agent] (38.4s) → PONG 🦞
|
|
232
|
+
✗ failed (404) (0.9s) → Model ... is not recognized by AutoClaw upstream
|
|
233
|
+
```
|
|
115
234
|
|
|
116
|
-
|
|
235
|
+
`[cloud ok]` means the cloud served it. `[cloud NNN → local agent]` means the cloud rejected it (HTTP NNN) and AutoClaw's desktop-agent fallback answered. This uses isolated log files, so it never clobbers your running proxy's records.
|
|
236
|
+
|
|
237
|
+
</details>
|
|
238
|
+
|
|
239
|
+
<details>
|
|
240
|
+
<summary><h2>Configuration (all flags & env vars)</h2></summary>
|
|
117
241
|
|
|
118
242
|
| Variable / Flag | Default | Description |
|
|
119
243
|
|-----------------|---------|-------------|
|
|
120
244
|
| `PORT` / `--port` | `18791` (OpenAI), `18792` (Anthropic) | Port this proxy listens on |
|
|
121
245
|
| `HOST` / `--host` | `127.0.0.1` | Bind address |
|
|
122
|
-
| `PROXY_KEY` / `--key` | `mewmew` | API key clients must send |
|
|
246
|
+
| `PROXY_KEY` / `--key` | `mewmew` | API key clients must send. Fine for localhost, change it when binding beyond loopback |
|
|
123
247
|
| `RATE_LIMIT` / `--rate-limit` | `30` | Max requests per second per client IP |
|
|
124
|
-
| `MAX_MESSAGES` / `--max-messages` | unlimited (`0`/unset) | Max message
|
|
248
|
+
| `MAX_MESSAGES` / `--max-messages` | unlimited (`0`/unset) | Max message/entity limit in request payload (explicit values: 128, 256, 512, 1024). Leave unlimited if your harness compresses or batches history, raise it if you hit `413 / payload too large` |
|
|
125
249
|
| `LOG_LEVEL` | `info` | `debug` / `info` / `silent` |
|
|
126
250
|
| `PREFER_LOCAL` | off | Set to `1` to use the local AutoClaw gateway first, skipping cloud attempts |
|
|
127
251
|
| `TRUSTED_PROXIES` | empty | Comma-separated IPs whose `X-Forwarded-For` header is trusted for rate limiting |
|
|
128
252
|
| `MAX_BODY_BYTES` | `52428800` | Max request body (50 MB) |
|
|
129
|
-
| `JSONL_LOG` | off | Write structured JSONL request log when `true` |
|
|
253
|
+
| `JSONL_LOG` | off | Write structured JSONL request log when `true` (also on with `LOG_LEVEL=debug`) |
|
|
130
254
|
| `JSONL_FILE` | `proxy_requests.jsonl` (Anthropic: `proxy_requests_anthropic.jsonl`) | JSONL output path |
|
|
255
|
+
| `JSONL_SYNC` | off | Write JSONL lines synchronously when `true` (flush every line) |
|
|
131
256
|
| `JSONL_MAX_BYTES` | `10485760` | Rotate JSONL log when it exceeds this (10 MB) |
|
|
132
|
-
| `UPSTREAM_TIMEOUT_MS` | `120000` | Per-attempt upstream budget (idle-based
|
|
133
|
-
| `GATEWAY_MIN_PROTOCOL` / `GATEWAY_MAX_PROTOCOL` | `3` / `4` | Local-gateway WS protocol range offered on connect (
|
|
257
|
+
| `UPSTREAM_TIMEOUT_MS` | `120000` | Per-attempt upstream budget (idle-based, the vendor allows up to 20 min, raise this for slow thinking models) |
|
|
258
|
+
| `GATEWAY_MIN_PROTOCOL` / `GATEWAY_MAX_PROTOCOL` | `3` / `4` | Local-gateway WS protocol range offered on connect (self-heals to the gateway's expected protocol on mismatch) |
|
|
134
259
|
| `LOCAL_GATEWAY_HOST` / `LOCAL_GATEWAY_PORT` | `127.0.0.1` / `18789` | Where the AutoClaw desktop gateway is expected |
|
|
135
|
-
| `FALLBACK_MODELS_PATH` | empty | Path to an external fallback model catalog JSON (`{"models":[...]}`)
|
|
260
|
+
| `FALLBACK_MODELS_PATH` | empty | Path to an external fallback model catalog JSON (`{"models":[...]}`), defaults to the shipped `lib/fallback-models.json` |
|
|
136
261
|
| `AUTOCLAW_SYSTEM_BANNER` | built-in | Override the system-prompt banner injected into cloud requests (keep the `## Tooling` line intact) |
|
|
137
262
|
| `--anthropic` | — | Run in Anthropic API format |
|
|
138
263
|
| `--openai` | — | Run in OpenAI API format (default) |
|
|
264
|
+
| `--limit [n]` | — | Set or clear the max message/entity limit (e.g. `--limit 256`; bare `--limit` prints the current value) |
|
|
139
265
|
| `--doctor` | — | Scan AutoClaw's current runtime model catalog and show Anthropic routing |
|
|
140
266
|
| `--test-models` / `--test` | — | Live health check: test every catalog model through the full pipeline |
|
|
141
267
|
| `--help`, `-h` | — | Show CLI help |
|
|
142
268
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
Set `JSONL_LOG=true` (or `LOG_LEVEL=debug`) to write one JSON line per request:
|
|
269
|
+
**JSONL request logging.** Set `JSONL_LOG=true` (or `LOG_LEVEL=debug`) to write one JSON line per request:
|
|
146
270
|
|
|
147
271
|
```json
|
|
148
272
|
{"ts":"2026-07-29T03:41:00.000Z","model":"zai_auto","status":200,"ip":"127.0.0.1","latencyMs":423}
|
|
149
273
|
```
|
|
150
274
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
Alongside the JSONL stream, a compact ring log (`proxy_requests.json`, last 50 entries) records every terminal outcome — including `via: "local"` and the cloud verdict (`cloud_status` / `cloud_error`) when the cloud rejected a request that the local agent ended up serving.
|
|
154
|
-
|
|
155
|
-
### Model doctor
|
|
156
|
-
|
|
157
|
-
Run the doctor to scan AutoClaw's live model catalog with **credit tiers** fetched from its remote model-config (falling back to the runtime file, then built-ins), and print the Claude alias routing map computed by the same resolver the Anthropic proxy uses:
|
|
158
|
-
|
|
159
|
-
```bash
|
|
160
|
-
node bin/cli.js --doctor
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Anthropic routing follows credit tiers: opus → High, sonnet → Medium, haiku → Low. UI display names can differ from API ids (the API's `zaicoding_glm-5.3` shows as "GLM-5.2" in AutoClaw's UI).
|
|
164
|
-
|
|
165
|
-
### Model health test
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
node bin/cli.js --test-models
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Spawns a throwaway proxy on a test port and fires a minimal prompt at **every model in the catalog**, reporting live status per model:
|
|
172
|
-
|
|
173
|
-
```
|
|
174
|
-
✔ working [cloud ok] (1.2s) → PONG
|
|
175
|
-
✔ working [cloud 402 → local agent] (38.4s) → PONG 🦞
|
|
176
|
-
✗ failed (404) (0.9s) → Model ... is not recognized by AutoClaw upstream
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
The cloud verdict comes from the isolated test ring log: a `[cloud ok]` tag means the cloud served it; `[cloud NNN → local agent]` means the cloud rejected it (HTTP NNN) and AutoClaw's desktop-agent fallback answered. Zero-usage responses are the local-agent signature. The test uses isolated log files so it never clobbers your running proxy's records.
|
|
180
|
-
|
|
181
|
-
## Self-hosting behind a reverse proxy
|
|
182
|
-
|
|
183
|
-
The proxy binds to `127.0.0.1` by default. To run it on a server (e.g. a VPS) and expose it with TLS, bind to all interfaces and put a reverse proxy in front:
|
|
275
|
+
Alongside the JSONL stream, a compact ring log (`proxy_requests.json`, last 50 entries; path via `REQUEST_LOG_FILE`) records every terminal outcome, including `via: "local"` and the cloud verdict (`cloud_status` / `cloud_error`) when the cloud rejected a request the local agent ended up serving.
|
|
184
276
|
|
|
185
|
-
|
|
186
|
-
node bin/cli.js --host 0.0.0.0 --port 18791 --key change-me
|
|
187
|
-
```
|
|
277
|
+
</details>
|
|
188
278
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
```caddy
|
|
192
|
-
glm.example.com {
|
|
193
|
-
reverse_proxy 127.0.0.1:18791
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
**nginx**:
|
|
198
|
-
|
|
199
|
-
```nginx
|
|
200
|
-
server {
|
|
201
|
-
listen 443 ssl;
|
|
202
|
-
server_name glm.example.com;
|
|
203
|
-
# ssl_certificate / ssl_certificate_key ...
|
|
204
|
-
|
|
205
|
-
location / {
|
|
206
|
-
proxy_pass http://127.0.0.1:18791;
|
|
207
|
-
proxy_set_header X-Forwarded-For $remote_addr;
|
|
208
|
-
proxy_set_header Host $host;
|
|
209
|
-
proxy_http_version 1.1;
|
|
210
|
-
proxy_set_header Connection ""; # keep streaming (SSE) working
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
Rate limiting keys off the client IP. When proxied, pass `X-Forwarded-For` and list the proxy's address in `TRUSTED_PROXIES` (comma-separated) so the real client IP is used — otherwise every client shares one bucket:
|
|
216
|
-
|
|
217
|
-
```bash
|
|
218
|
-
TRUSTED_PROXIES=127.0.0.1 node bin/cli.js --host 0.0.0.0
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
> The proxy needs a logged-in AutoClaw account running on the same machine (it reads the local token file), so a public endpoint is effectively a shared account — only expose it to people you trust.
|
|
222
|
-
|
|
223
|
-
## API
|
|
279
|
+
<details>
|
|
280
|
+
<summary><h2>API reference</h2></summary>
|
|
224
281
|
|
|
225
282
|
### `GET /healthz`
|
|
226
283
|
|
|
227
|
-
Returns token status and upstream info.
|
|
228
|
-
|
|
229
284
|
```json
|
|
230
285
|
{
|
|
231
286
|
"ok": true,
|
|
@@ -237,49 +292,24 @@ Returns token status and upstream info.
|
|
|
237
292
|
|
|
238
293
|
### `GET /v1/models`
|
|
239
294
|
|
|
240
|
-
Lists available models in OpenAI format (OpenAI proxy) or Anthropic format (Anthropic proxy).
|
|
295
|
+
Lists available models in OpenAI format (OpenAI proxy) or Anthropic format (Anthropic proxy). Re-read from AutoClaw's runtime file on every call, so no restart is needed when AutoClaw's model list changes.
|
|
241
296
|
|
|
242
|
-
|
|
243
|
-
{
|
|
244
|
-
"object": "list",
|
|
245
|
-
"data": [
|
|
246
|
-
{
|
|
247
|
-
"id": "zai_auto",
|
|
248
|
-
"object": "model",
|
|
249
|
-
"owned_by": "autoclaw",
|
|
250
|
-
"name": "Auto",
|
|
251
|
-
"context_window": 1048576,
|
|
252
|
-
"max_tokens": 393216
|
|
253
|
-
}
|
|
254
|
-
]
|
|
255
|
-
}
|
|
256
|
-
```
|
|
297
|
+
### `POST /v1/chat/completions` (OpenAI proxy)
|
|
257
298
|
|
|
258
|
-
|
|
299
|
+
Supports streaming (`stream: true`) and non-streaming.
|
|
259
300
|
|
|
260
|
-
### `POST /v1/chat/completions` — OpenAI proxy
|
|
261
|
-
|
|
262
|
-
OpenAI-compatible chat completions. Supports both streaming (`stream: true`) and non-streaming.
|
|
263
|
-
|
|
264
|
-
**Headers:**
|
|
265
301
|
```
|
|
266
302
|
Authorization: Bearer mewmew
|
|
267
303
|
Content-Type: application/json
|
|
268
304
|
```
|
|
269
305
|
|
|
270
|
-
### `POST /v1/messages`
|
|
306
|
+
### `POST /v1/messages` (Anthropic proxy)
|
|
271
307
|
|
|
272
|
-
Anthropic-compatible Messages API. Supports both streaming and non-streaming.
|
|
308
|
+
Anthropic-compatible Messages API. Supports both streaming and non-streaming. See the models table above for Claude → GLM routing.
|
|
273
309
|
|
|
274
|
-
|
|
275
|
-
|---|---|
|
|
276
|
-
| `claude-opus-*` | First available GLM-5.3 / GLM-5 model |
|
|
277
|
-
| `claude-sonnet-*` | `zai_auto` (or next available GLM-5 model) |
|
|
278
|
-
| `claude-haiku-*` | `zai_glm-5-turbo` (or DeepSeek / Auto fallback) |
|
|
310
|
+
### Error handling
|
|
279
311
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
Every failure maps to a semantically correct status with a machine-readable `code` — no more generic blobs:
|
|
312
|
+
Every failure maps to a semantically correct status with a machine-readable `code`:
|
|
283
313
|
|
|
284
314
|
| Situation | HTTP | `code` |
|
|
285
315
|
|-----------|------|--------|
|
|
@@ -290,138 +320,81 @@ Every failure maps to a semantically correct status with a machine-readable `cod
|
|
|
290
320
|
| Upstream rate limit | `429` | `rate_limited_by_upstream` |
|
|
291
321
|
| Upstream returned garbage or died | `502` | `upstream_failure` |
|
|
292
322
|
| AutoClaw not running (no token file) | `503` | `no_token` |
|
|
293
|
-
| Upstream timeout (2 min) | `504` | `upstream_timeout` |
|
|
323
|
+
| Upstream timeout (default 2 min, see `UPSTREAM_TIMEOUT_MS`) | `504` | `upstream_timeout` |
|
|
294
324
|
|
|
295
|
-
Quota errors are remembered for 60s per model
|
|
325
|
+
Quota errors are remembered for 60s per model, so repeat requests fail instantly instead of replaying doomed cloud and fallback attempts.
|
|
296
326
|
|
|
297
|
-
|
|
327
|
+
</details>
|
|
298
328
|
|
|
299
|
-
|
|
329
|
+
<details>
|
|
330
|
+
<summary><h2>Local gateway fallback</h2></summary>
|
|
300
331
|
|
|
301
|
-
|
|
332
|
+
When the cloud upstream fails (and it's not a plain 404/429), the proxy re-runs your prompt through AutoClaw's own desktop agent over a local WebSocket (`127.0.0.1:18789`). Responses served this way are logged with `via: "local"`. It's a full agentic run (slower, tools included) and shares your account's credits, so quota walls stop it too. Set `PREFER_LOCAL=1` to skip the cloud attempt entirely while credits are exhausted.
|
|
302
333
|
|
|
303
|
-
|
|
304
|
-
|----|------|---------|------------|-------|
|
|
305
|
-
| `zai_auto` | Auto | 1M | 393K | Routes to AutoClaw's optimal model |
|
|
306
|
-
| `zaicoding_glm-5.3` | GLM-5.3 | 1M | 307K | Latest GLM coding model |
|
|
307
|
-
| `zai_glm-5-turbo` | GLM-5-Turbo | 200K | 131K | Zhipu AI GLM-5 Turbo |
|
|
308
|
-
| `zai_glm-5.3-flash` | GLM-5.3-Flash ("OX-alpha") | 1M | 131K | Newest GLM flash model. The cloud upstream 400s it, so it's served via the local-agent fallback; it now appears in `/v1/models` and in the built-in fallback catalog |
|
|
309
|
-
| `tdpsk_deepseek-v4-flash-202605` | Deepseek-V4-Flash | 1M | 393K | Fast DeepSeek model |
|
|
310
|
-
| `tdpsk_deepseek-v4-pro-202606` | DeepSeek-V4-Pro | 1M | 393K | Deep reasoning model |
|
|
334
|
+
</details>
|
|
311
335
|
|
|
312
|
-
>
|
|
336
|
+
<details>
|
|
337
|
+
<summary><h2>Self-hosting behind a reverse proxy</h2></summary>
|
|
313
338
|
|
|
314
|
-
|
|
339
|
+
The proxy binds to `127.0.0.1` by default. To run it on a server and expose it with TLS, bind to all interfaces and put a reverse proxy in front:
|
|
315
340
|
|
|
316
|
-
|
|
341
|
+
```bash
|
|
342
|
+
glmproxy --host 0.0.0.0 --port 18791 --key change-me
|
|
343
|
+
```
|
|
317
344
|
|
|
318
|
-
|
|
345
|
+
**Caddy** (automatic HTTPS):
|
|
319
346
|
|
|
320
|
-
```
|
|
321
|
-
{
|
|
322
|
-
|
|
323
|
-
"autoclaw": {
|
|
324
|
-
"npm": "@ai-sdk/openai-compatible",
|
|
325
|
-
"name": "AutoClaw",
|
|
326
|
-
"options": {
|
|
327
|
-
"baseURL": "http://localhost:18791/v1",
|
|
328
|
-
"apiKey": "mewmew"
|
|
329
|
-
},
|
|
330
|
-
"models": {
|
|
331
|
-
"zai_auto": { "name": "AutoClaw Auto" },
|
|
332
|
-
"zaicoding_glm-5.3": { "name": "AutoClaw GLM-5.3" },
|
|
333
|
-
"zai_glm-5-turbo": { "name": "AutoClaw GLM-5 Turbo" },
|
|
334
|
-
"tdpsk_deepseek-v4-flash-202605": { "name": "AutoClaw Deepseek-V4-Flash" },
|
|
335
|
-
"tdpsk_deepseek-v4-pro-202606": { "name": "AutoClaw DeepSeek-V4-Pro" }
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
347
|
+
```caddy
|
|
348
|
+
glm.example.com {
|
|
349
|
+
reverse_proxy 127.0.0.1:18791
|
|
339
350
|
}
|
|
340
351
|
```
|
|
341
352
|
|
|
342
|
-
|
|
343
|
-
- **API Format**: OpenAI Chat Completions
|
|
344
|
-
- **URL**: `http://localhost:18791/v1`
|
|
345
|
-
- **Model ID**: `zai_auto` (or any model from the table above)
|
|
346
|
-
- **API Key**: `mewmew`
|
|
347
|
-
|
|
348
|
-
### Claude Code CLI
|
|
353
|
+
**nginx**:
|
|
349
354
|
|
|
350
|
-
|
|
355
|
+
```nginx
|
|
356
|
+
server {
|
|
357
|
+
listen 443 ssl;
|
|
358
|
+
server_name glm.example.com;
|
|
359
|
+
# ssl_certificate / ssl_certificate_key ...
|
|
351
360
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
361
|
+
location / {
|
|
362
|
+
proxy_pass http://127.0.0.1:18791;
|
|
363
|
+
proxy_set_header X-Forwarded-For $remote_addr;
|
|
364
|
+
proxy_set_header Host $host;
|
|
365
|
+
proxy_http_version 1.1;
|
|
366
|
+
proxy_set_header Connection ""; # keep streaming (SSE) working
|
|
367
|
+
}
|
|
358
368
|
}
|
|
359
369
|
```
|
|
360
370
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
```python
|
|
364
|
-
from openai import OpenAI
|
|
365
|
-
|
|
366
|
-
client = OpenAI(base_url="http://localhost:18791/v1", api_key="mewmew")
|
|
371
|
+
Rate limiting keys off the client IP. When proxied, pass `X-Forwarded-For` and list the proxy's address in `TRUSTED_PROXIES` so the real client IP is used, otherwise every client shares one bucket:
|
|
367
372
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
model="zai_auto",
|
|
371
|
-
messages=[{"role": "user", "content": "Hello!"}],
|
|
372
|
-
) as stream:
|
|
373
|
-
for text in stream.text_stream:
|
|
374
|
-
print(text, end="", flush=True)
|
|
375
|
-
|
|
376
|
-
# Non-streaming
|
|
377
|
-
response = client.chat.completions.create(
|
|
378
|
-
model="zai_auto",
|
|
379
|
-
messages=[{"role": "user", "content": "What is 2+2?"}],
|
|
380
|
-
stream=False,
|
|
381
|
-
)
|
|
382
|
-
print(response.choices[0].message.content)
|
|
373
|
+
```bash
|
|
374
|
+
TRUSTED_PROXIES=127.0.0.1 glmproxy --host 0.0.0.0
|
|
383
375
|
```
|
|
384
376
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
```javascript
|
|
388
|
-
import OpenAI from "openai";
|
|
389
|
-
|
|
390
|
-
const client = new OpenAI({
|
|
391
|
-
baseURL: "http://localhost:18791/v1",
|
|
392
|
-
apiKey: "mewmew",
|
|
393
|
-
});
|
|
377
|
+
> The proxy needs a logged-in AutoClaw account running on the same machine (it reads the local token file), so a public endpoint is effectively a shared account. Only expose it to people you trust.
|
|
394
378
|
|
|
395
|
-
|
|
396
|
-
model: "zai_auto",
|
|
397
|
-
messages: [{ role: "user", content: "Hello!" }],
|
|
398
|
-
stream: true,
|
|
399
|
-
});
|
|
379
|
+
</details>
|
|
400
380
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
```
|
|
381
|
+
<details>
|
|
382
|
+
<summary><h2>Good to know</h2></summary>
|
|
405
383
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
384
|
+
- Only one AutoClaw account can be active at a time, multi-account pooling isn't supported
|
|
385
|
+
- `PROXY_KEY` is just a local password for this proxy, not your AutoClaw credentials. Set it to whatever you want. The default `mewmew` is for localhost-only use
|
|
386
|
+
- On a 401, the proxy invalidates its cached token and you can retry immediately
|
|
387
|
+
- Upstream 400 `"invalid request"` gets one retry after a 2s delay (a known upstream hiccup). Quota/plan errors are never retried
|
|
388
|
+
- The cloud upstream requires AutoClaw's app system-prompt banner in every request. The proxy injects it automatically and never duplicates it. In practice it doesn't change much since your harness's own system prompt overrides it anyway
|
|
389
|
+
- The token file is watched for changes, so AutoClaw can rotate auth mid-session without a restart
|
|
390
|
+
- AutoClaw's client identity (app version, platform, channel) loads dynamically from its runtime file, same as the model catalog, so an AutoClaw app update is picked up without editing or restarting the proxy
|
|
391
|
+
- The fallback model catalog lives in `lib/fallback-models.json` (override with `FALLBACK_MODELS_PATH`). The built-in list is only a last resort when AutoClaw's runtime file is unreadable
|
|
392
|
+
- The local-gateway connection self-heals: if the gateway bumps its WS protocol, the proxy reconnects with the expected version automatically
|
|
393
|
+
- No dependencies at all. The interactive menu is hand-rolled on Node's built-in `readline`, so there's zero `node_modules` and zero install step
|
|
409
394
|
|
|
410
|
-
|
|
395
|
+
</details>
|
|
411
396
|
|
|
412
|
-
|
|
413
|
-
- `PROXY_KEY` is just a local password for this proxy, not your AutoClaw credentials — set it to whatever you want. The default key `mewmew` is for **localhost-only use**: change it via `--key` / `PROXY_KEY` if you bind beyond `127.0.0.1` (see [Self-hosting](#self-hosting-behind-a-reverse-proxy))
|
|
414
|
-
- On a 401, the proxy invalidates its cached token and you can retry immediately
|
|
415
|
-
- Upstream 400 "invalid request" gets one retry after a 2s delay (a known upstream hiccup); quota/plan errors are never retried
|
|
416
|
-
- The cloud upstream requires AutoClaw's app system-prompt banner in every request. their new verification — the proxy injects it automatically (and never duplicates it). but it doesnt change much in practice/ my own testing and others testing. since it will be overridden by the harnesses own system prompt.
|
|
417
|
-
- When cloud fails, requests fall back to AutoClaw's local desktop agent (`via: "local"` in logs) unless the model just failed permanently there too
|
|
418
|
-
- The token file is watched for changes — AutoClaw can rotate auth mid-session without a restart
|
|
419
|
-
- AutoClaw's client identity (`X-Version` app version, platform, channel) is loaded dynamically from its runtime file — the same file that feeds the model catalog — so an AutoClaw app update is picked up without editing or restarting the proxy
|
|
420
|
-
- Rate limit is enforced per client IP (default 30 req/s); X-Forwarded-For is only honored from `TRUSTED_PROXIES`
|
|
421
|
-
- The fallback model catalog lives in `lib/fallback-models.json` (override with `FALLBACK_MODELS_PATH`) — the built-in list is only a last resort when AutoClaw's runtime file is unreadable
|
|
422
|
-
- The local-gateway connect self-heals: if the gateway bumps its WS protocol, the proxy reconnects with the expected version automatically
|
|
423
|
-
- The ring log records cloud verdicts alongside local fallbacks, so every response is attributable
|
|
424
|
-
- No dependencies at all: the interactive menu is hand-rolled on Node's built-in `readline`, so there's zero `node_modules` and zero install step
|
|
397
|
+
---
|
|
425
398
|
|
|
426
399
|
## Special Thanks
|
|
427
400
|
|
|
@@ -431,4 +404,4 @@ Any tool that supports OpenAI-compatible providers works. Point it at `http://lo
|
|
|
431
404
|
|
|
432
405
|
## License
|
|
433
406
|
|
|
434
|
-
MIT License + Jarona Rights™ (sorry to keep u waiting)
|
|
407
|
+
MIT License + Jarona Rights™ (sorry to keep u waiting)
|