openclaw-autoproxy 1.0.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 ADDED
@@ -0,0 +1,210 @@
1
+ # openclaw-autoproxy (OpenClaw Auto Gateway)
2
+
3
+ Local proxy gateway that forwards OpenAI-compatible APIs and automatically switches model IDs when upstream returns retryable status codes (for example 412).
4
+
5
+ ## Features
6
+
7
+ - OpenAI-compatible proxy endpoint: `/v1/*`
8
+ - Automatic model fallback on retryable statuses for `model: auto` only (default: 412, 429, 500, 502, 503, 504)
9
+ - Model-based route selection: different models can use different upstream URLs and auth headers
10
+ - Per-model and global fallback chains
11
+ - TypeScript runtime powered by `tsx`
12
+ - Node.js HTTP gateway server (openclaw-style)
13
+ - Cross-platform startup on macOS and Windows (Node.js 18+)
14
+ - Health endpoint: `/health`
15
+
16
+ ## Quick Start
17
+
18
+ 1. Install Node.js 18 or newer.
19
+ 2. Install dependencies:
20
+
21
+ ```bash
22
+ npm install
23
+ ```
24
+
25
+ 3. Create local env file:
26
+
27
+ macOS/Linux:
28
+
29
+ ```bash
30
+ cp .env.example .env
31
+ ```
32
+
33
+ Windows PowerShell:
34
+
35
+ ```powershell
36
+ Copy-Item .env.example .env
37
+ ```
38
+
39
+ 4. Edit `.env` (runtime options) and `routes.yml` (all upstream route mappings and auth).
40
+ 5. Start the gateway:
41
+
42
+ ```bash
43
+ npm run dev
44
+ ```
45
+
46
+ Production mode:
47
+
48
+ ```bash
49
+ npm start
50
+ ```
51
+
52
+ ## Global CLI Usage
53
+
54
+ You can install this project globally and run it via `openclaw-autoproxy`:
55
+
56
+ ```bash
57
+ npm i -g .
58
+ openclaw-autoproxy gateway start
59
+ ```
60
+
61
+ Watch mode:
62
+
63
+ ```bash
64
+ openclaw-autoproxy gateway dev
65
+ ```
66
+
67
+ Show CLI help:
68
+
69
+ ```bash
70
+ openclaw-autoproxy gateway help
71
+ ```
72
+
73
+ Backward-compatible aliases are still supported:
74
+
75
+ ```bash
76
+ openclaw-autoproxy start
77
+ openclaw-autoproxy dev
78
+ openclaw-autoproxy help
79
+ ```
80
+
81
+ ## OpenAI-Compatible Calls For 3 Models
82
+
83
+ After starting gateway locally, always call the local OpenAI-style endpoint:
84
+
85
+ ```bash
86
+ curl -X POST http://127.0.0.1:8787/v1/chat/completions \
87
+ -H "Content-Type: application/json" \
88
+ -d '{
89
+ "model": "GLM-4.7-Flash",
90
+ "messages": [{"role":"user","content":"你好"}]
91
+ }'
92
+ ```
93
+
94
+ ```bash
95
+ curl -X POST http://127.0.0.1:8787/v1/chat/completions \
96
+ -H "Content-Type: application/json" \
97
+ -d '{
98
+ "model": "doubao-seed-2-0-pro-260215",
99
+ "messages": [{"role":"user","content":"你好"}]
100
+ }'
101
+ ```
102
+
103
+ ```bash
104
+ curl -X POST http://127.0.0.1:8787/v1/chat/completions \
105
+ -H "Content-Type: application/json" \
106
+ -d '{
107
+ "model": "ernie-4.5-turbo-128k",
108
+ "messages": [{"role":"user","content":"你好"}]
109
+ }'
110
+ ```
111
+
112
+ ## API
113
+
114
+ - `ALL /v1/*`: Forward to upstream; automatic model fallback is used only when request model is `auto`.
115
+ - `GET /health`: Health check and active retry status list.
116
+
117
+ ## Project Structure
118
+
119
+ ```text
120
+ src/
121
+ gateway/
122
+ config.ts
123
+ proxy.ts
124
+ server-http.ts
125
+ server.impl.ts
126
+ server.ts
127
+ ```
128
+
129
+ ### Example Chat Request
130
+
131
+ ```bash
132
+ curl -X POST http://127.0.0.1:8787/v1/chat/completions \
133
+ -H "Content-Type: application/json" \
134
+ -H "Authorization: Bearer <your-upstream-token>" \
135
+ -d '{
136
+ "model": "gpt-4.1",
137
+ "messages": [{"role": "user", "content": "hello"}],
138
+ "temperature": 0.2
139
+ }'
140
+ ```
141
+
142
+ Then call local gateway:
143
+
144
+ ```bash
145
+ curl -X POST http://127.0.0.1:8787/v1/chat/completions \
146
+ -H "Content-Type: application/json" \
147
+ -d '{
148
+ "model": "GLM-4.7-Flash",
149
+ "messages": [{"role":"user","content":"你好"}]
150
+ }'
151
+ ```
152
+
153
+ ### Helpful Response Headers
154
+
155
+ - `x-gateway-model-used`: The actual model used by this attempt.
156
+ - `x-gateway-attempt-count`: Number of attempts before returning response.
157
+ - `x-gateway-switched`: `1` when model fallback happened in this response.
158
+
159
+ ### Switch Notice In Response Data
160
+
161
+ - JSON response: when fallback happened, gateway appends `gateway_notice` at top-level JSON.
162
+ - SSE response: when fallback happened, gateway prepends one event:
163
+
164
+ ```text
165
+ event: gateway_notice
166
+ data: {"fromModel":"...","toModel":"...","triggerStatus":412,...}
167
+ ```
168
+
169
+ ## Fallback Strategy
170
+
171
+ The gateway behavior is split by request model:
172
+
173
+ 1. `model != auto`: pinned mode, only the requested model is used (no automatic switch).
174
+ 2. `model == auto`: automatic mode, candidates are all enabled route models from `routes.yml`, and each request uses a round-robin start model.
175
+
176
+ When upstream returns a status in `retryStatusCodes` (from `routes.yml`), automatic mode retries using the next candidate model in the same rotated list. If this key is absent, it falls back to `RETRY_STATUS_CODES` env.
177
+
178
+ ## Model Route Configuration
179
+
180
+ `routes.yml` is loaded automatically from the project root.
181
+
182
+ Recommended YAML shape:
183
+
184
+ - `defaults`: optional global auth defaults used by all routes
185
+ - `retryStatusCodes`: optional array of retryable HTTP status codes (for example `[412, 429, 500, 502, 503, 504]`)
186
+ - `routes`: required array of route objects
187
+
188
+ Top-level array is also supported when you do not need global defaults.
189
+
190
+ Each route object supports:
191
+
192
+ - `name`: optional logical route name
193
+ - `url`: upstream URL
194
+ - `model`: model list (or a single string)
195
+ - `authHeader`: optional auth header name
196
+ - `authPrefix`: optional auth value prefix (default `Bearer `)
197
+ - `apiKey`: inline token value (preferred in this setup)
198
+ - `apiKeyEnv`: optional env-based token fallback
199
+ - `headers`: optional fixed headers map
200
+ - `isBaseUrl`: optional boolean to force base URL behavior
201
+ - `enabled`: optional boolean (default `true`), set `false` to disable the route without deleting it
202
+
203
+ `routes.yml` is required and loaded from the project root.
204
+
205
+ ## Notes
206
+
207
+ - If client request already includes `Authorization`, gateway forwards it.
208
+ - If client request does not include `Authorization`, gateway uses `UPSTREAM_API_KEY`.
209
+ - Streaming responses are forwarded as stream when an attempt succeeds.
210
+ - Requests with invalid JSON body return `400`.
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from "node:child_process";
4
+ import { existsSync } from "node:fs";
5
+ import { readFile } from "node:fs/promises";
6
+ import path from "node:path";
7
+ import process from "node:process";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ const args = process.argv.slice(2);
11
+
12
+ const currentFilePath = fileURLToPath(import.meta.url);
13
+ const packageRoot = path.resolve(path.dirname(currentFilePath), "..");
14
+ const serverEntryPath = path.join(packageRoot, "src", "gateway", "server.ts");
15
+ const tsxCliPath = path.join(packageRoot, "node_modules", "tsx", "dist", "cli.mjs");
16
+ const packageJsonPath = path.join(packageRoot, "package.json");
17
+
18
+ function printHelp() {
19
+ console.log(`openclaw-autoproxy - OpenClaw Auto Gateway CLI
20
+
21
+ Usage:
22
+ openclaw-autoproxy gateway start
23
+ openclaw-autoproxy gateway dev
24
+ openclaw-autoproxy start
25
+ openclaw-autoproxy dev
26
+ openclaw-autoproxy help
27
+ openclaw-autoproxy --version
28
+
29
+ Commands:
30
+ gateway Gateway command group
31
+ start Legacy alias of: openclaw-autoproxy gateway start
32
+ dev Legacy alias of: openclaw-autoproxy gateway dev
33
+ help Show root help
34
+ `);
35
+ }
36
+
37
+ function printGatewayHelp() {
38
+ console.log(`openclaw-autoproxy gateway - Gateway command group
39
+
40
+ Usage:
41
+ openclaw-autoproxy gateway start
42
+ openclaw-autoproxy gateway dev
43
+ openclaw-autoproxy gateway help
44
+
45
+ Subcommands:
46
+ start Start gateway server (default)
47
+ dev Start gateway in watch mode
48
+ help Show gateway help
49
+ `);
50
+ }
51
+
52
+ async function printVersion() {
53
+ try {
54
+ const raw = await readFile(packageJsonPath, "utf8");
55
+ const pkg = JSON.parse(raw);
56
+ console.log(pkg.version ?? "unknown");
57
+ } catch {
58
+ console.log("unknown");
59
+ }
60
+ }
61
+
62
+ function runTsxMode(tsxArgs) {
63
+ if (!existsSync(tsxCliPath)) {
64
+ console.error("Missing tsx runtime. Reinstall dependencies and try again.");
65
+ process.exit(1);
66
+ }
67
+
68
+ const child = spawn(process.execPath, [tsxCliPath, ...tsxArgs], {
69
+ stdio: "inherit",
70
+ cwd: process.cwd(),
71
+ env: process.env,
72
+ });
73
+
74
+ child.on("exit", (code, signal) => {
75
+ if (signal) {
76
+ process.kill(process.pid, signal);
77
+ return;
78
+ }
79
+
80
+ process.exit(code ?? 0);
81
+ });
82
+ }
83
+
84
+ function runDevMode(extraArgs) {
85
+ runTsxMode(["watch", serverEntryPath, ...extraArgs]);
86
+ }
87
+
88
+ function runStartMode(extraArgs) {
89
+ runTsxMode([serverEntryPath, ...extraArgs]);
90
+ }
91
+
92
+ function isHelpFlag(value) {
93
+ return value === "help" || value === "--help" || value === "-h";
94
+ }
95
+
96
+ function isVersionFlag(value) {
97
+ return value === "version" || value === "--version" || value === "-v";
98
+ }
99
+
100
+ function resolveGatewayAction(rawArgs) {
101
+ const subcommand = rawArgs[1] ?? "start";
102
+
103
+ if (isHelpFlag(subcommand)) {
104
+ return { type: "gateway-help" };
105
+ }
106
+
107
+ if (subcommand === "start") {
108
+ return {
109
+ type: "gateway-start",
110
+ passthrough: rawArgs.slice(2),
111
+ };
112
+ }
113
+
114
+ if (subcommand === "dev") {
115
+ return {
116
+ type: "gateway-dev",
117
+ passthrough: rawArgs.slice(2),
118
+ };
119
+ }
120
+
121
+ return {
122
+ type: "error",
123
+ message: `Unknown gateway subcommand: ${subcommand}`,
124
+ };
125
+ }
126
+
127
+ function resolveAction(rawArgs) {
128
+ const command = rawArgs[0];
129
+
130
+ if (!command) {
131
+ return {
132
+ type: "gateway-start",
133
+ passthrough: [],
134
+ };
135
+ }
136
+
137
+ if (isHelpFlag(command)) {
138
+ return { type: "root-help" };
139
+ }
140
+
141
+ if (isVersionFlag(command)) {
142
+ return { type: "version" };
143
+ }
144
+
145
+ if (command === "gateway") {
146
+ return resolveGatewayAction(rawArgs);
147
+ }
148
+
149
+ if (command === "start") {
150
+ return {
151
+ type: "gateway-start",
152
+ passthrough: rawArgs.slice(1),
153
+ };
154
+ }
155
+
156
+ if (command === "dev") {
157
+ return {
158
+ type: "gateway-dev",
159
+ passthrough: rawArgs.slice(1),
160
+ };
161
+ }
162
+
163
+ return {
164
+ type: "error",
165
+ message: `Unknown command: ${command}`,
166
+ };
167
+ }
168
+
169
+ async function main() {
170
+ const action = resolveAction(args);
171
+
172
+ if (action.type === "root-help") {
173
+ printHelp();
174
+ return;
175
+ }
176
+
177
+ if (action.type === "gateway-help") {
178
+ printGatewayHelp();
179
+ return;
180
+ }
181
+
182
+ if (action.type === "version") {
183
+ await printVersion();
184
+ return;
185
+ }
186
+
187
+ if (action.type === "gateway-start") {
188
+ runStartMode(action.passthrough);
189
+ return;
190
+ }
191
+
192
+ if (action.type === "gateway-dev") {
193
+ runDevMode(action.passthrough);
194
+ return;
195
+ }
196
+
197
+ console.error(action.message);
198
+ printHelp();
199
+ process.exitCode = 1;
200
+ }
201
+
202
+ main().catch((error) => {
203
+ console.error(
204
+ error instanceof Error
205
+ ? `Failed to run openclaw-autoproxy: ${error.message}`
206
+ : "Failed to run openclaw-autoproxy due to an unknown error.",
207
+ );
208
+ process.exit(1);
209
+ });
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "openclaw-autoproxy",
3
+ "version": "1.0.0",
4
+ "description": "Local model-switching proxy gateway with OpenAI-compatible APIs",
5
+ "type": "module",
6
+ "main": "src/gateway/server.ts",
7
+ "bin": {
8
+ "openclaw-autoproxy": "bin/openclaw-autoproxy.js"
9
+ },
10
+ "scripts": {
11
+ "cli": "node bin/openclaw-autoproxy.js",
12
+ "start": "node bin/openclaw-autoproxy.js gateway start",
13
+ "dev": "node bin/openclaw-autoproxy.js gateway dev",
14
+ "typecheck": "tsc --noEmit",
15
+ "test": "echo \"No tests configured\""
16
+ },
17
+ "keywords": [
18
+ "proxy",
19
+ "gateway",
20
+ "openclaw-autoproxy",
21
+ "openai-compatible",
22
+ "fallback"
23
+ ],
24
+ "author": "",
25
+ "license": "ISC",
26
+ "dependencies": {
27
+ "dotenv": "^17.4.0",
28
+ "tsx": "^4.20.6",
29
+ "yaml": "^2.8.3"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^24.6.1",
33
+ "typescript": "^5.9.3"
34
+ }
35
+ }