flexgate 0.5.1__tar.gz → 0.7.0__tar.gz
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.
- {flexgate-0.5.1 → flexgate-0.7.0}/CLAUDE.md +24 -13
- {flexgate-0.5.1 → flexgate-0.7.0}/PKG-INFO +20 -11
- {flexgate-0.5.1 → flexgate-0.7.0}/README.md +19 -10
- {flexgate-0.5.1 → flexgate-0.7.0}/docs/cli.md +45 -16
- {flexgate-0.5.1 → flexgate-0.7.0}/docs/configuration.md +1 -1
- {flexgate-0.5.1 → flexgate-0.7.0}/docs/development.md +9 -7
- {flexgate-0.5.1 → flexgate-0.7.0}/docs/quickstart.md +1 -1
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/__init__.py +1 -1
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/cli.py +179 -115
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/config.py +12 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/doctor.py +40 -12
- flexgate-0.7.0/flexgate/fallback.py +99 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/healthcheck.py +2 -23
- flexgate-0.7.0/flexgate/images.py +192 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/main.py +0 -1
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/proxy.py +5 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/router.py +28 -2
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/server.py +13 -1
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/service.py +180 -140
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/settings.py +114 -67
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/sync.py +14 -17
- flexgate-0.7.0/flexgate/ui.py +99 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/update.py +19 -18
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/usage.py +2 -3
- flexgate-0.5.1/flexgate/guardian.py +0 -252
- {flexgate-0.5.1 → flexgate-0.7.0}/.github/workflows/docs.yml +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/.github/workflows/release.yml +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/.gitignore +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/config.yaml.template +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/docs/Makefile +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/docs/_static/.gitkeep +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/docs/conf.py +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/docs/index.md +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/docs/requirements.txt +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/docs/usage.md +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/__main__.py +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/migrate.py +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/pyproject.toml +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/test.png +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/test_multimodal.py +0 -0
- {flexgate-0.5.1 → flexgate-0.7.0}/test_multimodal_gateway.py +0 -0
|
@@ -21,8 +21,8 @@ flexgate service install # install + enable + start systemd u
|
|
|
21
21
|
# Lifecycle
|
|
22
22
|
flexgate service {install|start|stop|restart|reload|status|uninstall}
|
|
23
23
|
flexgate run # foreground/debug only
|
|
24
|
-
flexgate check # provider connectivity diagnostics
|
|
25
24
|
flexgate status # providers, fallback chains, per-key usage, active routes
|
|
25
|
+
flexgate log # service journal (-f follow, -r route lines only)
|
|
26
26
|
|
|
27
27
|
# Config
|
|
28
28
|
flexgate config {init|show|set|path|edit}
|
|
@@ -31,8 +31,9 @@ flexgate config set sonnet minimax MiniMax-M3
|
|
|
31
31
|
flexgate config edit # interactive curses TUI: pick provider/model per tier
|
|
32
32
|
|
|
33
33
|
# Claude Code settings bridge
|
|
34
|
-
flexgate settings import # read from ~/.claude/settings.json
|
|
35
|
-
flexgate settings apply #
|
|
34
|
+
flexgate settings import # read from ~/.claude/settings.json* (merges keys into api_keys)
|
|
35
|
+
flexgate settings apply # rewrite the flexgate env keys → localhost (other fields preserved)
|
|
36
|
+
flexgate settings apply --dry-run # preview the env diff without writing
|
|
36
37
|
|
|
37
38
|
# confsync config sync
|
|
38
39
|
flexgate sync # pull: replace local config.yaml with the remote document (backup first)
|
|
@@ -41,7 +42,9 @@ flexgate sync --dry-run # preview changes without writing
|
|
|
41
42
|
|
|
42
43
|
# Versioning / upgrades
|
|
43
44
|
flexgate --version # print package version
|
|
44
|
-
flexgate doctor # diagnose install/config
|
|
45
|
+
flexgate doctor # diagnose install/config + upstream connectivity (exit 1 on failure)
|
|
46
|
+
flexgate doctor --offline # skip all network checks (PyPI + upstream probing)
|
|
47
|
+
flexgate check # DEPRECATED alias: delegates to doctor
|
|
45
48
|
flexgate update # upgrade package via pip/pipx/uv + migrate config schema
|
|
46
49
|
flexgate update --check # report what would change, modify nothing
|
|
47
50
|
flexgate update --config-only # only migrate the config schema
|
|
@@ -70,35 +73,41 @@ without a systemd user instance.
|
|
|
70
73
|
|
|
71
74
|
```
|
|
72
75
|
Claude Code → POST /v1/messages (model="claude-sonnet-4-6")
|
|
73
|
-
→ server.py (Starlette app,
|
|
76
|
+
→ server.py (Starlette app, /v1/messages + /v1/images/* routes)
|
|
74
77
|
→ router.py (resolve(): regex match model, check schedule windows first, then default routes)
|
|
75
78
|
→ proxy.py (rewrite model field if override, swap x-api-key, SSE streaming pass-through)
|
|
76
79
|
→ Upstream provider (z.ai, minimax, xiaomi, etc.)
|
|
80
|
+
|
|
81
|
+
OpenAI-style client → POST /v1/images/generations
|
|
82
|
+
→ images.py (translate to MiniMax /v1/image_generation; provider auto-detected by base_url)
|
|
77
83
|
```
|
|
78
84
|
|
|
79
85
|
### Source files (`flexgate/`)
|
|
80
86
|
|
|
81
87
|
| File | Role |
|
|
82
88
|
|------|------|
|
|
83
|
-
| `cli.py` | argparse CLI; service lifecycle commands, foreground `run
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
89
|
+
| `cli.py` | argparse CLI; service lifecycle commands, foreground `run`, config/settings/sync commands, `flexgate log` journal viewer |
|
|
90
|
+
| `ui.py` | Shared terminal styling: ANSI helpers (`bold`/`dim`/`red`/...), `ok`/`warn`/`fail`/`err` output helpers, `FlexgateHelpFormatter` + `FlexgateParser` (colored help on every Python version; all gated by `NO_COLOR` + stdout isatty) |
|
|
91
|
+
| `config.py` | Pydantic-like dataclasses (`GatewayConfig`, `ProviderConfig`, `RouteConfig`, `ScheduleEntry`), YAML load/save, `TIER_PATTERNS` regex map, `is_placeholder_key` |
|
|
92
|
+
| `router.py` | `resolve(config, model)` — schedule-first then default routes, first regex match wins; model aliases normalized before matching |
|
|
86
93
|
| `proxy.py` | `handle_request()` — httpx async proxy, per-key fallback retry loop, SSE streaming + JSON pass-through |
|
|
87
94
|
| `usage.py` | `flexgate status` usage queries — per-platform adapters (MiniMax coding_plan API, Kimi Code usages API, z.ai quota API, LiteLLM `/key/info`) plus a minimal chat probe fallback |
|
|
88
|
-
| `server.py` | Starlette app creation, `POST /v1/messages`
|
|
95
|
+
| `server.py` | Starlette app creation, `POST /v1/messages` + `POST /v1/images/{generations,edits}` endpoints, `SIGUSR1` lifespan reload |
|
|
96
|
+
| `images.py` | OpenAI Images API → MiniMax `/v1/image_generation` translation (`image-01`, size→width/height clamped to 512–2048 mult of 8, `data.image_base64[]`→`data[].b64_json`); edits endpoint returns 501; unknown image model names get 501 `model_not_supported` |
|
|
97
|
+
| `fallback.py` | Catch-all route: merged Anthropic/OpenAI-shaped JSON errors for every unserved path — 404 `route_not_found` (lists valid routes), 405 `method_not_allowed`, 501 `endpoint_not_supported` per recognized API family with an actionable reason |
|
|
89
98
|
| `main.py` | Thin bootstrap: load config → create app → run uvicorn |
|
|
90
99
|
| `service.py` | Authoritative systemd user-service install/start/stop/restart/reload/status and legacy PID migration |
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `settings.py` | Bridges `config.yaml` ↔ `~/.claude/settings.json` (import credentials, apply config) |
|
|
100
|
+
| `healthcheck.py` | Upstream connectivity probing (`POST /v1/messages`, max_tokens=1) for each referenced (provider, model) pair; called by `doctor` (`flexgate check` is a deprecated alias of doctor) |
|
|
101
|
+
| `settings.py` | Bridges `config.yaml` ↔ `~/.claude/settings.json`: import merges credentials into `api_keys`; apply rewrites only the managed env keys, preserving every other settings.json field |
|
|
94
102
|
| `sync.py` | `flexgate sync` — pushes/pulls the whole config.yaml as an encrypted document on a confsync server (lazily imports the `confsync` client package) |
|
|
95
103
|
| `migrate.py` | Config schema versioning: `config_version` marker, per-step `MIGRATIONS` chain (N → N+1), backup + atomic rewrite |
|
|
96
|
-
| `doctor.py` | `flexgate doctor` — read-only diagnostics (Python, PyPI update, config schema/semantics, port, systemd, Claude settings) |
|
|
104
|
+
| `doctor.py` | `flexgate doctor` — read-only diagnostics (Python, PyPI update, config schema/semantics, port, systemd, Claude settings) plus upstream connectivity via `healthcheck` (skipped with `--offline`) |
|
|
97
105
|
| `update.py` | `flexgate update` — PyPI version check, package upgrade via detected installer (pipx/uv/pip), config migration, service reload; after a successful package upgrade the config migration is re-run in a fresh process so it uses the new code's schema version (`FLEXGATE_UPDATE_DELEGATED` guards against re-delegation); also the cached (24h) new-version notice shown by bare `flexgate` / `service status` |
|
|
98
106
|
|
|
99
107
|
### Key design points
|
|
100
108
|
|
|
101
109
|
- **Regex-first routing**: Routes are regex patterns matched against the `model` field in the request body. First match wins. A catch-all `".*"` pattern at the end handles fallback.
|
|
110
|
+
- **Model alias normalization** (`router.py`): before matching, bare aliases (`sonnet`/`opus`/`haiku`/`default`, case-insensitive, optional `[plan]` suffix) map to `claude-<tier>` prefixes (`default` → sonnet, Claude Code's default tier), and legacy numbered names (`claude-3-7-sonnet-latest`) reduce to their tier prefix; everything else passes through unchanged, so custom patterns and the catch-all behave exactly as before.
|
|
102
111
|
- **Model resolution & `available_models` fallback**: A route may omit `model`; `router.resolve()` then falls back to the provider's first `available_models` entry, so `model_override` handed to the proxy is always a concrete name. `config._parse_routes` rejects routes that omit `model` on a provider with no `available_models` — so adding a provider without models requires an explicit `model` on every route using it.
|
|
103
112
|
- **Proxy rewrite contract** (`proxy.py`): the upstream request gets the provider's `x-api-key` plus a fixed header set, and the JSON `model` field is rewritten only when the route set an override. Streaming responses are forwarded as raw bytes (`aiter_bytes`), never parsed.
|
|
104
113
|
- **Key fallback** (`proxy.py`): a provider's `api_keys` list holds one or more keys for the same upstream (entries are key strings or `{key, note}` mappings; `note` is a free-form label shown by `flexgate status` and in fallback logs). Multiple accounts on one upstream — e.g. several MiniMax subscriptions — belong in ONE provider, not separate ones. Each *route* has an active-key pointer (`active_key` in YAML, 1-based; `RouteConfig.key_index` 0-based, default first key): requests on that route start from the pointed key, and on a retryable failure (HTTP 401/402/403/429/500/502/503/529, connect error, or timeout) the next key is tried and the pointer advances with it (in memory only, wrapping around — it is not written back to the file); every key is tried at most once and a full circle of failures returns the last error. The pointer is set interactively via `flexgate config edit` → "api keys" (pick a route, with live per-key usage) or by editing the route's `active_key` directly. `resolve()` (`router.py`) returns the matched `RouteConfig` so the proxy can read/advance its pointer. Streaming requests can only fall back before the upstream returns 200. `_regular_proxy`/`_stream_proxy` return `(response, retryable)`; `handle_request` owns the loop and returns the last error when all keys fail. The legacy `api_key` + `fallback_keys` schema is still parsed (`config._parse_api_keys`); migration v3 → v4 rewrites it to `api_keys`. `ProviderConfig.api_key` remains as a first-key property (getter/setter) for the settings import code.
|
|
@@ -110,6 +119,8 @@ Claude Code → POST /v1/messages (model="claude-sonnet-4-6")
|
|
|
110
119
|
- **Conflict prevention**: Service startup removes stale legacy PID files, stops verified legacy Flexgate daemons, validates the configured port, and rejects temporary config paths.
|
|
111
120
|
- **Tier patterns** in `config.py`: `opus`, `sonnet`, `haiku` map to regex patterns for CLI shorthand (`config set sonnet ...`).
|
|
112
121
|
- **Single-source versioning**: the package version lives only in `flexgate/__init__.py` (`__version__`); hatchling reads it via `[tool.hatch.version]`. `--version`, `service status` and the bare `flexgate` command all print it.
|
|
122
|
+
- **Settings apply is non-destructive** (`settings.py`): only the six managed `env` keys are rewritten (BASE_URL, AUTH_TOKEN, API_TIMEOUT_MS, three `ANTHROPIC_DEFAULT_*_MODEL`); every other settings.json field (hooks, permissions, unrelated env vars) is preserved. Token policy is one code path shared with `service install`: explicit arg → token already in the file → `"gateway"`. `settings import` merges into the provider's `api_keys` list (appends new keys, never overwrites key #1 only).
|
|
123
|
+
- **Terminal styling** (`ui.py`): every colored output goes through `ui.bold`/`dim`/`ok`/`err`/... — all gated on `NO_COLOR` + stdout isatty, so pipes never receive escape codes. `err()` writes to stderr. Help screens use `FlexgateParser` (subparsers inherit it automatically); on Python 3.14+ argparse's own help theming is disabled so the look matches older versions.
|
|
113
124
|
- **Config schema versioning**: `config.yaml` carries `config_version` (current: `migrate.CURRENT_CONFIG_VERSION`). Each schema change adds one rule to `migrate.MIGRATIONS` upgrading N → N+1; upgrades walk the chain step by step. `save_config` always stamps the current version; `load_config` rejects configs written by a newer flexgate; `flexgate update` applies pending migrations with a timestamped backup.
|
|
114
125
|
|
|
115
126
|
### Config location
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: flexgate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Flexible local API gateway for Claude Code — route models to different providers
|
|
5
5
|
Project-URL: Homepage, https://github.com/Agony5757/flexible-gateway
|
|
6
6
|
Project-URL: Repository, https://github.com/Agony5757/flexible-gateway
|
|
@@ -106,13 +106,13 @@ flexgate service uninstall # 停止、禁用并删除 unit
|
|
|
106
106
|
- 若升级时检测到旧版后台 gateway 仍在运行,会先准备好 systemd unit,但不会强杀正在服务的进程;按提示手动 `kill <PID>` 停掉旧进程,再执行 `flexgate service start` 完成切换。
|
|
107
107
|
- unit 设置了启动速率限制,永久配置错误不会再无限快速重启。
|
|
108
108
|
- `service reload` 和 `config set/edit` 会在仅路由变化时发送 SIGUSR1;如果 endpoint 变化,则先检查再 restart。若只改 host、仍复用当前 port,为避免误停服务会要求先执行 `service stop`,再执行 `service start`。
|
|
109
|
-
- 查看日志:`
|
|
109
|
+
- 查看日志:`flexgate log`(`-f` 跟随、`--since`/`--grep` 过滤、`-r` 只看路由决策行)。
|
|
110
110
|
|
|
111
111
|
### 版本与升级
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
114
|
flexgate --version # 打印版本号(service status / 裸 flexgate 也会显示)
|
|
115
|
-
flexgate doctor # 只读体检:Python、PyPI 新版、配置 schema、端口、systemd、Claude settings
|
|
115
|
+
flexgate doctor # 只读体检:Python、PyPI 新版、配置 schema、端口、systemd、Claude settings、上游连通性
|
|
116
116
|
flexgate doctor --offline # 跳过 PyPI 检查
|
|
117
117
|
flexgate update # 一键升级:pip/pipx/uv 升级包 + 迁移配置 schema + 热重载服务
|
|
118
118
|
flexgate update --check # 只报告将要做什么,不改动
|
|
@@ -149,7 +149,7 @@ Trusted Publisher(repo: `Agony5757/flexible-gateway`,workflow: `release.yml`
|
|
|
149
149
|
|
|
150
150
|
### 上游连通性预检
|
|
151
151
|
|
|
152
|
-
运行 `flexgate
|
|
152
|
+
运行 `flexgate doctor` 会向每个 **被路由引用的 `(provider, model)` 组合**
|
|
153
153
|
发送一次 `POST /v1/messages`(`max_tokens=1`,消耗约 1~2 token),用于主动检查:
|
|
154
154
|
|
|
155
155
|
- DNS / TCP / TLS 不可达(`base_url` 写错、网络不通)
|
|
@@ -157,7 +157,10 @@ Trusted Publisher(repo: `Agony5757/flexible-gateway`,workflow: `release.yml`
|
|
|
157
157
|
- 仍是默认占位符(如 `your-zai-api-key`)
|
|
158
158
|
- Provider 侧 5xx 故障
|
|
159
159
|
|
|
160
|
-
可通过 `--verify-timeout N` 调整每个 provider 的超时时间(默认 15
|
|
160
|
+
可通过 `--verify-timeout N` 调整每个 provider 的超时时间(默认 15 秒),
|
|
161
|
+
`--offline` 跳过全部网络检查(PyPI 新版检查 + 上游探测)。
|
|
162
|
+
旧的 `flexgate check` 命令仍可用,但已弃用:它会打印提示并委托给 `doctor`
|
|
163
|
+
(注意 doctor 还检查本地安装,退出码语义更宽)。
|
|
161
164
|
|
|
162
165
|
### 状态总览与用量查询(`flexgate status` / `flexgate usage`)
|
|
163
166
|
|
|
@@ -187,13 +190,12 @@ fallback 链、当前生效的路由,并逐一查询**每个 key** 的用量/
|
|
|
187
190
|
如果某平台的专用接口调用失败,flexgate 会自动退化为 minimal probe 再试一次。
|
|
188
191
|
用量查询接口多为平台内部接口,可能随时变动;查询结果仅供参考。
|
|
189
192
|
|
|
190
|
-
###
|
|
193
|
+
### 前台调试
|
|
191
194
|
|
|
192
|
-
`run`
|
|
195
|
+
`run` 是独立的顶层调试命令,不属于持久化服务模式:
|
|
193
196
|
|
|
194
197
|
```bash
|
|
195
198
|
flexgate run # 单个前台进程,仅用于开发/调试
|
|
196
|
-
flexgate check # 上游 provider 连通性检测
|
|
197
199
|
```
|
|
198
200
|
|
|
199
201
|
非 systemd 环境只能使用 `flexgate run` 前台运行。`--port PORT` 也只对
|
|
@@ -259,10 +261,17 @@ Flexgate config — ~/.flexgate/config.yaml
|
|
|
259
261
|
### Settings 管理
|
|
260
262
|
|
|
261
263
|
```bash
|
|
262
|
-
flexgate settings import # 从 ~/.claude/settings.json* 导入凭证到 config.yaml
|
|
263
|
-
flexgate settings apply #
|
|
264
|
+
flexgate settings import # 从 ~/.claude/settings.json* 导入凭证到 config.yaml(多 key 追加)
|
|
265
|
+
flexgate settings apply # 将网关 env 写入 ~/.claude/settings.json(非破坏性)
|
|
266
|
+
flexgate settings apply --dry-run # 预览将要修改的 env 键,不写文件
|
|
264
267
|
```
|
|
265
268
|
|
|
269
|
+
`apply` 只重写 flexgate 托管的 6 个 env 键(BASE_URL、AUTH_TOKEN、
|
|
270
|
+
API_TIMEOUT_MS、三个 `ANTHROPIC_DEFAULT_*_MODEL`),settings.json 中的
|
|
271
|
+
其他字段(hooks、permissions、自定义 env 等)全部保留;写入前自动备份。
|
|
272
|
+
`import` 会把新 key 追加到 provider 的 `api_keys` 列表(已存在则跳过),
|
|
273
|
+
不会覆盖已有 key。
|
|
274
|
+
|
|
266
275
|
### 全局参数
|
|
267
276
|
|
|
268
277
|
- `--config PATH` 指定配置文件(默认 `~/.flexgate/config.yaml`)
|
|
@@ -278,7 +287,7 @@ flexgate settings apply # 将 config.yaml 配置写入 ~/.claude/settin
|
|
|
278
287
|
| `~/.flexgate/service-state.json` | 最近一次成功启动所应用的 config 路径与 endpoint |
|
|
279
288
|
| `~/.flexgate/update-check.json` | PyPI 新版本检查的缓存(24h 有效期) |
|
|
280
289
|
| `~/.config/systemd/user/flexgate.service` | 唯一的持久化服务 unit |
|
|
281
|
-
| systemd journal | 服务日志(`journalctl --user -u flexgate`) |
|
|
290
|
+
| systemd journal | 服务日志(`flexgate log`,即 `journalctl --user -u flexgate`) |
|
|
282
291
|
|
|
283
292
|
旧版本的 `~/.flexgate/flexgate.pid`、`flexgate.guardian.pid` 和
|
|
284
293
|
`flexgate.log` 不再属于当前运行架构;service 启动时会安全清理 PID 残留,
|
|
@@ -89,13 +89,13 @@ flexgate service uninstall # 停止、禁用并删除 unit
|
|
|
89
89
|
- 若升级时检测到旧版后台 gateway 仍在运行,会先准备好 systemd unit,但不会强杀正在服务的进程;按提示手动 `kill <PID>` 停掉旧进程,再执行 `flexgate service start` 完成切换。
|
|
90
90
|
- unit 设置了启动速率限制,永久配置错误不会再无限快速重启。
|
|
91
91
|
- `service reload` 和 `config set/edit` 会在仅路由变化时发送 SIGUSR1;如果 endpoint 变化,则先检查再 restart。若只改 host、仍复用当前 port,为避免误停服务会要求先执行 `service stop`,再执行 `service start`。
|
|
92
|
-
- 查看日志:`
|
|
92
|
+
- 查看日志:`flexgate log`(`-f` 跟随、`--since`/`--grep` 过滤、`-r` 只看路由决策行)。
|
|
93
93
|
|
|
94
94
|
### 版本与升级
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
97
|
flexgate --version # 打印版本号(service status / 裸 flexgate 也会显示)
|
|
98
|
-
flexgate doctor # 只读体检:Python、PyPI 新版、配置 schema、端口、systemd、Claude settings
|
|
98
|
+
flexgate doctor # 只读体检:Python、PyPI 新版、配置 schema、端口、systemd、Claude settings、上游连通性
|
|
99
99
|
flexgate doctor --offline # 跳过 PyPI 检查
|
|
100
100
|
flexgate update # 一键升级:pip/pipx/uv 升级包 + 迁移配置 schema + 热重载服务
|
|
101
101
|
flexgate update --check # 只报告将要做什么,不改动
|
|
@@ -132,7 +132,7 @@ Trusted Publisher(repo: `Agony5757/flexible-gateway`,workflow: `release.yml`
|
|
|
132
132
|
|
|
133
133
|
### 上游连通性预检
|
|
134
134
|
|
|
135
|
-
运行 `flexgate
|
|
135
|
+
运行 `flexgate doctor` 会向每个 **被路由引用的 `(provider, model)` 组合**
|
|
136
136
|
发送一次 `POST /v1/messages`(`max_tokens=1`,消耗约 1~2 token),用于主动检查:
|
|
137
137
|
|
|
138
138
|
- DNS / TCP / TLS 不可达(`base_url` 写错、网络不通)
|
|
@@ -140,7 +140,10 @@ Trusted Publisher(repo: `Agony5757/flexible-gateway`,workflow: `release.yml`
|
|
|
140
140
|
- 仍是默认占位符(如 `your-zai-api-key`)
|
|
141
141
|
- Provider 侧 5xx 故障
|
|
142
142
|
|
|
143
|
-
可通过 `--verify-timeout N` 调整每个 provider 的超时时间(默认 15
|
|
143
|
+
可通过 `--verify-timeout N` 调整每个 provider 的超时时间(默认 15 秒),
|
|
144
|
+
`--offline` 跳过全部网络检查(PyPI 新版检查 + 上游探测)。
|
|
145
|
+
旧的 `flexgate check` 命令仍可用,但已弃用:它会打印提示并委托给 `doctor`
|
|
146
|
+
(注意 doctor 还检查本地安装,退出码语义更宽)。
|
|
144
147
|
|
|
145
148
|
### 状态总览与用量查询(`flexgate status` / `flexgate usage`)
|
|
146
149
|
|
|
@@ -170,13 +173,12 @@ fallback 链、当前生效的路由,并逐一查询**每个 key** 的用量/
|
|
|
170
173
|
如果某平台的专用接口调用失败,flexgate 会自动退化为 minimal probe 再试一次。
|
|
171
174
|
用量查询接口多为平台内部接口,可能随时变动;查询结果仅供参考。
|
|
172
175
|
|
|
173
|
-
###
|
|
176
|
+
### 前台调试
|
|
174
177
|
|
|
175
|
-
`run`
|
|
178
|
+
`run` 是独立的顶层调试命令,不属于持久化服务模式:
|
|
176
179
|
|
|
177
180
|
```bash
|
|
178
181
|
flexgate run # 单个前台进程,仅用于开发/调试
|
|
179
|
-
flexgate check # 上游 provider 连通性检测
|
|
180
182
|
```
|
|
181
183
|
|
|
182
184
|
非 systemd 环境只能使用 `flexgate run` 前台运行。`--port PORT` 也只对
|
|
@@ -242,10 +244,17 @@ Flexgate config — ~/.flexgate/config.yaml
|
|
|
242
244
|
### Settings 管理
|
|
243
245
|
|
|
244
246
|
```bash
|
|
245
|
-
flexgate settings import # 从 ~/.claude/settings.json* 导入凭证到 config.yaml
|
|
246
|
-
flexgate settings apply #
|
|
247
|
+
flexgate settings import # 从 ~/.claude/settings.json* 导入凭证到 config.yaml(多 key 追加)
|
|
248
|
+
flexgate settings apply # 将网关 env 写入 ~/.claude/settings.json(非破坏性)
|
|
249
|
+
flexgate settings apply --dry-run # 预览将要修改的 env 键,不写文件
|
|
247
250
|
```
|
|
248
251
|
|
|
252
|
+
`apply` 只重写 flexgate 托管的 6 个 env 键(BASE_URL、AUTH_TOKEN、
|
|
253
|
+
API_TIMEOUT_MS、三个 `ANTHROPIC_DEFAULT_*_MODEL`),settings.json 中的
|
|
254
|
+
其他字段(hooks、permissions、自定义 env 等)全部保留;写入前自动备份。
|
|
255
|
+
`import` 会把新 key 追加到 provider 的 `api_keys` 列表(已存在则跳过),
|
|
256
|
+
不会覆盖已有 key。
|
|
257
|
+
|
|
249
258
|
### 全局参数
|
|
250
259
|
|
|
251
260
|
- `--config PATH` 指定配置文件(默认 `~/.flexgate/config.yaml`)
|
|
@@ -261,7 +270,7 @@ flexgate settings apply # 将 config.yaml 配置写入 ~/.claude/settin
|
|
|
261
270
|
| `~/.flexgate/service-state.json` | 最近一次成功启动所应用的 config 路径与 endpoint |
|
|
262
271
|
| `~/.flexgate/update-check.json` | PyPI 新版本检查的缓存(24h 有效期) |
|
|
263
272
|
| `~/.config/systemd/user/flexgate.service` | 唯一的持久化服务 unit |
|
|
264
|
-
| systemd journal | 服务日志(`journalctl --user -u flexgate`) |
|
|
273
|
+
| systemd journal | 服务日志(`flexgate log`,即 `journalctl --user -u flexgate`) |
|
|
265
274
|
|
|
266
275
|
旧版本的 `~/.flexgate/flexgate.pid`、`flexgate.guardian.pid` 和
|
|
267
276
|
`flexgate.log` 不再属于当前运行架构;service 启动时会安全清理 PID 残留,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
```text
|
|
4
4
|
usage: flexgate [-h] [--config CONFIG] [--version]
|
|
5
|
-
{service,run,check,status,usage,settings,sync,help,doctor,update,config} ...
|
|
5
|
+
{service,run,check,status,usage,log,settings,sync,help,doctor,update,config} ...
|
|
6
6
|
```
|
|
7
7
|
|
|
8
8
|
全局参数:
|
|
@@ -45,7 +45,7 @@ flexgate service uninstall # 停止、禁用并删除 unit
|
|
|
45
45
|
- `service reload` 和 `config set/edit` 会在仅路由变化时发送 SIGUSR1;
|
|
46
46
|
如果 endpoint 变化,则先检查再 restart。若只改 host、仍复用当前 port,
|
|
47
47
|
为避免误停服务会要求先执行 `service stop`,再执行 `service start`。
|
|
48
|
-
- 查看日志:`
|
|
48
|
+
- 查看日志:`flexgate log`(`-f` 跟随、`--since`/`--grep` 过滤、`-r` 只看路由决策行)。
|
|
49
49
|
|
|
50
50
|
## 状态总览与用量查询
|
|
51
51
|
|
|
@@ -59,9 +59,32 @@ flexgate usage --usage-timeout 30
|
|
|
59
59
|
|
|
60
60
|
各平台的用量查询适配器详见[用量查询](usage.md)。
|
|
61
61
|
|
|
62
|
+
## 查看日志(log)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
flexgate log # 最近 50 行服务日志(systemd user journal)
|
|
66
|
+
flexgate log -n 200 # 最近 200 行
|
|
67
|
+
flexgate log -f # 实时跟随(Ctrl-C 退出)
|
|
68
|
+
flexgate log --since "10 min ago"
|
|
69
|
+
flexgate log --grep 429 # 只显示包含 429 的行(大小写不敏感)
|
|
70
|
+
flexgate log -r # 只看路由决策行
|
|
71
|
+
flexgate log -f -r # 实时观察每个请求被路由到哪个 provider/模型
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`flexgate log` 读取 `flexgate.service` 的 systemd 用户日志(等价于
|
|
75
|
+
`journalctl --user -u flexgate`,输出为单时间戳的 cat 格式)。`-r` 只显示
|
|
76
|
+
每个请求完成时的路由决策行:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
[default] claude-sonnet-4-6 -> zai (glm-5.3) | 200 | 6676ms
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
想知道"刚才那条回复到底是哪个模型服务的",看这一行即可。前台调试用的
|
|
83
|
+
`flexgate run` 日志直接打印在终端,不进 journal。
|
|
84
|
+
|
|
62
85
|
## 上游连通性预检
|
|
63
86
|
|
|
64
|
-
`flexgate
|
|
87
|
+
`flexgate doctor` 会向每个**被路由引用的 `(provider, model)` 组合**发送一次
|
|
65
88
|
`POST /v1/messages`(`max_tokens=1`,消耗约 1~2 token),用于主动检查:
|
|
66
89
|
|
|
67
90
|
- DNS / TCP / TLS 不可达(`base_url` 写错、网络不通)
|
|
@@ -69,15 +92,17 @@ flexgate usage --usage-timeout 30
|
|
|
69
92
|
- 仍是默认占位符(如 `your-zai-api-key`)
|
|
70
93
|
- Provider 侧 5xx 故障
|
|
71
94
|
|
|
72
|
-
可通过 `--verify-timeout N` 调整每个 provider
|
|
95
|
+
可通过 `--verify-timeout N` 调整每个 provider 的探测超时(默认 15 秒);
|
|
96
|
+
`--offline` 跳过全部网络检查(PyPI 新版检查 + 上游探测)。旧的
|
|
97
|
+
`flexgate check` 命令已弃用:仍可运行,但会打印提示并委托给 `doctor`
|
|
98
|
+
(注意 doctor 还检查本地安装,退出码语义更宽)。
|
|
73
99
|
|
|
74
100
|
## 前台调试
|
|
75
101
|
|
|
76
|
-
`run`
|
|
102
|
+
`run` 是独立的顶层调试命令,不属于持久化服务模式:
|
|
77
103
|
|
|
78
104
|
```bash
|
|
79
105
|
flexgate run # 单个前台进程,仅用于开发/调试
|
|
80
|
-
flexgate check # 上游 provider 连通性检测
|
|
81
106
|
```
|
|
82
107
|
|
|
83
108
|
非 systemd 环境只能使用 `flexgate run` 前台运行。`--port PORT` 也只对
|
|
@@ -157,22 +182,26 @@ Flexgate config — ~/.flexgate/config.yaml
|
|
|
157
182
|
## Settings 管理
|
|
158
183
|
|
|
159
184
|
```bash
|
|
160
|
-
flexgate settings import
|
|
161
|
-
flexgate settings apply
|
|
185
|
+
flexgate settings import # 从 ~/.claude/settings.json* 导入凭证到 config.yaml(多 key 追加)
|
|
186
|
+
flexgate settings apply # 将网关 env 写入 ~/.claude/settings.json(非破坏性)
|
|
187
|
+
flexgate settings apply --dry-run # 预览将要修改的 env 键,不写文件
|
|
162
188
|
```
|
|
163
189
|
|
|
164
190
|
**import** 会扫描 `~/.claude/settings.json*`,从每个文件中提取
|
|
165
|
-
`ANTHROPIC_BASE_URL` 和 `ANTHROPIC_AUTH_TOKEN
|
|
166
|
-
providers
|
|
191
|
+
`ANTHROPIC_BASE_URL` 和 `ANTHROPIC_AUTH_TOKEN`,合并进 config.yaml 的
|
|
192
|
+
providers:provider 已存在时**追加**到它的 `api_keys` 列表(已存在同一把
|
|
193
|
+
key 则跳过),不存在则新建。文件名与 provider 名称的映射规则:
|
|
167
194
|
|
|
168
195
|
- `settings.json` → 根据域名自动推断(如含 `z.ai` → `zai`)
|
|
169
196
|
- `settings.json.zai` → provider 名 `zai`
|
|
170
197
|
- `settings.json.bak.*` → 跳过(备份文件)
|
|
171
198
|
|
|
172
|
-
**apply**
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
`
|
|
199
|
+
**apply** 是非破坏性的:备份当前 `~/.claude/settings.json` 为
|
|
200
|
+
`settings.json.bak.{timestamp}` 后,**只重写 flexgate 托管的 6 个 env 键**
|
|
201
|
+
(`ANTHROPIC_BASE_URL`、`ANTHROPIC_AUTH_TOKEN`、`API_TIMEOUT_MS`、三个
|
|
202
|
+
`ANTHROPIC_DEFAULT_*_MODEL`),settings.json 中的其余字段(hooks、
|
|
203
|
+
permissions、自定义 env 等)全部原样保留。token 策略与 `service install`
|
|
204
|
+
共用一条路径:显式参数 → 文件里已有的 token → 兜底 `"gateway"`:
|
|
176
205
|
|
|
177
206
|
```json
|
|
178
207
|
{
|
|
@@ -211,8 +240,8 @@ flexgate sync --dry-run # 只预览,不写配置
|
|
|
211
240
|
|
|
212
241
|
```bash
|
|
213
242
|
flexgate --version # 打印版本号
|
|
214
|
-
flexgate doctor # 只读体检:Python、PyPI 新版、配置 schema、端口、systemd、Claude settings
|
|
215
|
-
flexgate doctor --offline #
|
|
243
|
+
flexgate doctor # 只读体检:Python、PyPI 新版、配置 schema、端口、systemd、Claude settings、上游连通性
|
|
244
|
+
flexgate doctor --offline # 跳过全部网络检查(PyPI 新版检查 + 上游探测)
|
|
216
245
|
flexgate update # 一键升级:pip/pipx/uv 升级包 + 迁移配置 schema + 热重载服务
|
|
217
246
|
flexgate update --check # 只报告将要做什么,不改动
|
|
218
247
|
flexgate update --config-only # 只迁移配置,不升级包
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
| `~/.flexgate/service-state.json` | 最近一次成功启动所应用的 config 路径与 endpoint |
|
|
9
9
|
| `~/.flexgate/update-check.json` | PyPI 新版本检查的缓存(24h 有效期) |
|
|
10
10
|
| `~/.config/systemd/user/flexgate.service` | 唯一的持久化服务 unit |
|
|
11
|
-
| systemd journal | 服务日志(`journalctl --user -u flexgate`) |
|
|
11
|
+
| systemd journal | 服务日志(`flexgate log`,即 `journalctl --user -u flexgate`) |
|
|
12
12
|
|
|
13
13
|
旧版本的 `~/.flexgate/flexgate.pid`、`flexgate.guardian.pid` 和
|
|
14
14
|
`flexgate.log` 不再属于当前运行架构;service 启动时会安全清理 PID 残留,
|
|
@@ -4,17 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
| 文件 | 作用 |
|
|
6
6
|
|------|------|
|
|
7
|
-
| `flexgate/cli.py` | argparse CLI;service 生命周期命令、前台 `run
|
|
8
|
-
| `flexgate/
|
|
9
|
-
| `flexgate/
|
|
10
|
-
| `flexgate/
|
|
7
|
+
| `flexgate/cli.py` | argparse CLI;service 生命周期命令、前台 `run`、config/settings/sync 命令、`flexgate log` 日志查看 |
|
|
8
|
+
| `flexgate/ui.py` | 共享终端样式:ANSI 辅助函数、`ok`/`warn`/`fail`/`err` 输出辅助、`FlexgateHelpFormatter`(各版本一致的彩色 help;统一受 `NO_COLOR` + isatty 门控) |
|
|
9
|
+
| `flexgate/config.py` | dataclass 配置模型(`GatewayConfig`、`ProviderConfig`、`RouteConfig` 等),YAML 加载/保存,`is_placeholder_key` |
|
|
10
|
+
| `flexgate/router.py` | `resolve(config, model)` — 先定时路由后默认路由,首个正则命中生效;匹配前归一化模型别名(`sonnet`/`claude-3-7-sonnet-latest` → `^claude-<tier>`) |
|
|
11
|
+
| `flexgate/proxy.py` | `handle_request()` — httpx 异步代理,per-key fallback 重试,SSE 流式 + JSON 透传;记录每个请求由哪把 key 服务 |
|
|
11
12
|
| `flexgate/usage.py` | `flexgate status` / `flexgate usage` 的用量查询 — 各平台适配器 + minimal chat probe 兜底 |
|
|
12
13
|
| `flexgate/server.py` | Starlette 应用,`POST /v1/messages` 端点,SIGUSR1 热重载 |
|
|
13
|
-
| `flexgate/service.py` | systemd
|
|
14
|
-
| `flexgate/
|
|
14
|
+
| `flexgate/service.py` | systemd 用户服务的安装/启停/重载/状态(持久化运行的唯一入口);journal 日志查看 |
|
|
15
|
+
| `flexgate/healthcheck.py` | 上游连通性探测(`POST /v1/messages`,`max_tokens=1`),由 `doctor` 调用(`flexgate check` 为弃用别名) |
|
|
16
|
+
| `flexgate/settings.py` | config.yaml ↔ `~/.claude/settings.json` 双向桥接(apply 非破坏性:只改托管 env 键) |
|
|
15
17
|
| `flexgate/sync.py` | `flexgate sync` — 通过 confsync 服务器加密同步整份配置 |
|
|
16
18
|
| `flexgate/migrate.py` | 配置 schema 版本化:逐级迁移链 + 备份 + 原子重写 |
|
|
17
|
-
| `flexgate/doctor.py` | `flexgate doctor` — 只读诊断 |
|
|
19
|
+
| `flexgate/doctor.py` | `flexgate doctor` — 只读诊断 + 上游连通性(`--offline` 跳过网络检查) |
|
|
18
20
|
| `flexgate/update.py` | `flexgate update` — PyPI 版本检查、按安装方式升级、配置迁移、服务重载 |
|
|
19
21
|
|
|
20
22
|
## 关键设计
|