flexgate 0.2.0__tar.gz → 0.3.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.2.0 → flexgate-0.3.0}/CLAUDE.md +5 -6
- {flexgate-0.2.0 → flexgate-0.3.0}/PKG-INFO +2 -1
- {flexgate-0.2.0 → flexgate-0.3.0}/config.yaml.template +0 -9
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/__init__.py +1 -1
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/cli.py +5 -7
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/config.py +0 -30
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/migrate.py +13 -1
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/sync.py +12 -31
- {flexgate-0.2.0 → flexgate-0.3.0}/pyproject.toml +1 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/.github/workflows/release.yml +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/.gitignore +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/README.md +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/__main__.py +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/doctor.py +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/guardian.py +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/healthcheck.py +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/main.py +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/proxy.py +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/router.py +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/server.py +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/service.py +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/settings.py +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/flexgate/update.py +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/test.png +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/test_multimodal.py +0 -0
- {flexgate-0.2.0 → flexgate-0.3.0}/test_multimodal_gateway.py +0 -0
|
@@ -112,15 +112,14 @@ Claude Code → POST /v1/messages (model="claude-sonnet-4-6")
|
|
|
112
112
|
### Config location
|
|
113
113
|
|
|
114
114
|
Config lives at `~/.flexgate/config.yaml` (override with `FLEXGATE_CONFIG`).
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
shared confsync credentials written by `confsync login --server <url>`
|
|
115
|
+
`flexgate sync` needs no flexgate-side setup — connection details come from
|
|
116
|
+
the shared confsync credentials written by `confsync login --server <url>`
|
|
118
117
|
(`~/.confsync/credentials.json`). The whole config.yaml is synced as one
|
|
119
|
-
encrypted document (
|
|
118
|
+
encrypted document (app `flexgate`, name `config.yaml`): pull merges
|
|
120
119
|
provider api_keys and imports remote-only providers (local routes/schedules
|
|
121
120
|
untouched), bootstraps the file when missing, and `--full` replaces it with a
|
|
122
|
-
timestamped backup. The confsync
|
|
123
|
-
|
|
121
|
+
timestamped backup. The `confsync-client` package is a declared dependency
|
|
122
|
+
(PyPI), so sync works out of the box once logged in.
|
|
124
123
|
The persistent unit lives at `~/.config/systemd/user/flexgate.service`; logs are
|
|
125
124
|
in the systemd user journal. `~/.flexgate/service-state.json` records the last
|
|
126
125
|
successfully applied config path and endpoint. PID/guardian files are legacy artifacts only.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: flexgate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -8,6 +8,7 @@ Project-URL: Issues, https://github.com/Agony5757/flexible-gateway/issues
|
|
|
8
8
|
Author: agony
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: confsync-client>=0.1.0
|
|
11
12
|
Requires-Dist: httpx[socks]>=0.27
|
|
12
13
|
Requires-Dist: pyyaml>=6.0
|
|
13
14
|
Requires-Dist: starlette>=0.37
|
|
@@ -43,15 +43,6 @@ claude_settings:
|
|
|
43
43
|
default_haiku_model: "claude-haiku-4-5"
|
|
44
44
|
api_timeout_ms: 3000000
|
|
45
45
|
|
|
46
|
-
# confsync 配置同步(可选):`flexgate sync` 把整个 config.yaml 作为加密文档
|
|
47
|
-
# 同步到你的 confsync 服务端。连接信息通常来自共享凭据(先执行
|
|
48
|
-
# `confsync login --server <url>`,凭据存于 ~/.confsync/credentials.json),
|
|
49
|
-
# flexgate 侧无需配置。下面的 confsync 段仅用于覆盖 server_url 或文档 app 名
|
|
50
|
-
# (默认 app 为 "flexgate",文档名 "config.yaml")。
|
|
51
|
-
# confsync:
|
|
52
|
-
# server_url: "https://confsync.example.com"
|
|
53
|
-
# app: "flexgate"
|
|
54
|
-
|
|
55
46
|
# 定时路由(可选):按时间自动切换模型配置,首个时间窗口命中生效
|
|
56
47
|
# schedule:
|
|
57
48
|
# - name: "xiaomi-discount"
|
|
@@ -700,17 +700,15 @@ def _print_sync_help() -> None:
|
|
|
700
700
|
print("""\
|
|
701
701
|
confsync sync (flexgate sync):
|
|
702
702
|
Pushes/pulls config.yaml as an encrypted document on your confsync server.
|
|
703
|
-
Requires
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
~/.confsync/credentials.json). 'flexgate sync' (pull) updates api_keys of
|
|
703
|
+
Requires a one-time 'confsync login --server https://<server>' (shared
|
|
704
|
+
credentials at ~/.confsync/credentials.json); the confsync-client package
|
|
705
|
+
is a declared dependency. 'flexgate sync' (pull) updates api_keys of
|
|
707
706
|
matching providers and imports providers present only remotely; local
|
|
708
707
|
routes/schedules are untouched. On a machine without config.yaml the pull
|
|
709
708
|
bootstraps the whole file. Use --full to replace the local config entirely
|
|
710
709
|
(a timestamped backup is kept), 'flexgate sync push' to upload, and
|
|
711
|
-
--dry-run to preview. The
|
|
712
|
-
|
|
713
|
-
Changes hot-reload the running service automatically.
|
|
710
|
+
--dry-run to preview. The document lives at app 'flexgate', name
|
|
711
|
+
'config.yaml'. Changes hot-reload the running service automatically.
|
|
714
712
|
""")
|
|
715
713
|
|
|
716
714
|
|
|
@@ -54,12 +54,6 @@ class ClaudeSettings:
|
|
|
54
54
|
api_timeout_ms: int = 3000000
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
@dataclass
|
|
58
|
-
class ConfsyncConfig:
|
|
59
|
-
server_url: str = ""
|
|
60
|
-
app: str = "flexgate"
|
|
61
|
-
|
|
62
|
-
|
|
63
57
|
@dataclass
|
|
64
58
|
class ScheduleEntry:
|
|
65
59
|
name: str
|
|
@@ -75,7 +69,6 @@ class GatewayConfig:
|
|
|
75
69
|
routes: list[RouteConfig] = field(default_factory=list)
|
|
76
70
|
schedule: list[ScheduleEntry] = field(default_factory=list)
|
|
77
71
|
claude_settings: ClaudeSettings = field(default_factory=ClaudeSettings)
|
|
78
|
-
confsync: ConfsyncConfig = field(default_factory=ConfsyncConfig)
|
|
79
72
|
|
|
80
73
|
|
|
81
74
|
def _parse_hhmm(value: str) -> int:
|
|
@@ -175,13 +168,6 @@ def load_config(path: str | None = None) -> GatewayConfig:
|
|
|
175
168
|
api_timeout_ms=cs.get("api_timeout_ms", cfg.claude_settings.api_timeout_ms),
|
|
176
169
|
)
|
|
177
170
|
|
|
178
|
-
cs2 = raw.get("confsync", {})
|
|
179
|
-
if cs2:
|
|
180
|
-
cfg.confsync = ConfsyncConfig(
|
|
181
|
-
server_url=str(cs2.get("server_url", "")),
|
|
182
|
-
app=str(cs2.get("app", "flexgate")),
|
|
183
|
-
)
|
|
184
|
-
|
|
185
171
|
return cfg
|
|
186
172
|
|
|
187
173
|
|
|
@@ -221,12 +207,6 @@ def save_config(cfg: GatewayConfig, path: str | None = None) -> None:
|
|
|
221
207
|
"routes": _serialize_routes(cfg.routes),
|
|
222
208
|
}
|
|
223
209
|
|
|
224
|
-
if cfg.confsync.server_url:
|
|
225
|
-
data["confsync"] = {
|
|
226
|
-
"server_url": cfg.confsync.server_url,
|
|
227
|
-
"app": cfg.confsync.app,
|
|
228
|
-
}
|
|
229
|
-
|
|
230
210
|
for name, prov in cfg.providers.items():
|
|
231
211
|
entry: dict = {
|
|
232
212
|
"base_url": prov.base_url,
|
|
@@ -305,16 +285,6 @@ claude_settings:
|
|
|
305
285
|
default_haiku_model: "claude-haiku-4-5"
|
|
306
286
|
api_timeout_ms: 3000000
|
|
307
287
|
|
|
308
|
-
# confsync config sync (optional): `flexgate sync` pushes/pulls this whole
|
|
309
|
-
# file as an encrypted document on your confsync server. Connection normally
|
|
310
|
-
# comes from the shared confsync credentials (`confsync login --server ...`,
|
|
311
|
-
# stored at ~/.confsync/credentials.json) — no flexgate-side setup needed.
|
|
312
|
-
# The section below is only needed to override the server or the document
|
|
313
|
-
# app name (default app: "flexgate", document name: "config.yaml").
|
|
314
|
-
# confsync:
|
|
315
|
-
# server_url: "https://confsync.example.com"
|
|
316
|
-
# app: "flexgate"
|
|
317
|
-
|
|
318
288
|
# Scheduled routes (optional): switch model config by time of day; the first
|
|
319
289
|
# matching time window wins.
|
|
320
290
|
# schedule:
|
|
@@ -18,7 +18,7 @@ import time
|
|
|
18
18
|
from dataclasses import dataclass, field
|
|
19
19
|
from typing import Callable
|
|
20
20
|
|
|
21
|
-
CURRENT_CONFIG_VERSION =
|
|
21
|
+
CURRENT_CONFIG_VERSION = 3
|
|
22
22
|
|
|
23
23
|
# ── per-step migration rules ──────────────────────────────────────
|
|
24
24
|
# Each rule takes the raw config dict at version N and returns it upgraded
|
|
@@ -51,10 +51,22 @@ def _migrate_1_to_2(data: dict) -> dict:
|
|
|
51
51
|
return data
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def _migrate_2_to_3(data: dict) -> dict:
|
|
55
|
+
"""Drop the removed `confsync:` section.
|
|
56
|
+
|
|
57
|
+
The confsync override section (server_url / app) was removed; sync now
|
|
58
|
+
always uses the shared confsync credentials (`confsync login --server ...`)
|
|
59
|
+
and the default document (app "flexgate", name "config.yaml").
|
|
60
|
+
"""
|
|
61
|
+
data.pop("confsync", None)
|
|
62
|
+
return data
|
|
63
|
+
|
|
64
|
+
|
|
54
65
|
# version N → rule upgrading N to N+1
|
|
55
66
|
MIGRATIONS: dict[int, Callable[[dict], dict]] = {
|
|
56
67
|
0: _migrate_0_to_1,
|
|
57
68
|
1: _migrate_1_to_2,
|
|
69
|
+
2: _migrate_2_to_3,
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Connection details come from the shared confsync credentials
|
|
4
4
|
(``confsync login --server <url>`` → ~/.confsync/credentials.json); the
|
|
5
|
-
|
|
6
|
-
and the document's app name (default app: "flexgate", name: "config.yaml").
|
|
5
|
+
document is fixed at app "flexgate", name "config.yaml".
|
|
7
6
|
|
|
8
7
|
Pull semantics:
|
|
9
8
|
* local config missing → bootstrap: the remote document becomes the config
|
|
@@ -13,7 +12,6 @@ Pull semantics:
|
|
|
13
12
|
"""
|
|
14
13
|
from __future__ import annotations
|
|
15
14
|
|
|
16
|
-
import json
|
|
17
15
|
import os
|
|
18
16
|
import shutil
|
|
19
17
|
import sys
|
|
@@ -31,9 +29,12 @@ def _import_confsync():
|
|
|
31
29
|
from confsync import credentials # noqa: PLC0415
|
|
32
30
|
return confsync, credentials
|
|
33
31
|
except ImportError:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
print("
|
|
32
|
+
# confsync-client is a declared dependency; reaching here means the
|
|
33
|
+
# installation is broken (e.g. an editable install predating the dep).
|
|
34
|
+
print("The 'confsync' client package is missing from flexgate's environment.")
|
|
35
|
+
print("Reinstall/upgrade flexgate to fix it (it is a declared dependency):")
|
|
36
|
+
print(" uv tool install --force -e . # from the flexgate repo")
|
|
37
|
+
print("or inject it manually: uv tool inject flexgate confsync-client")
|
|
37
38
|
sys.exit(1)
|
|
38
39
|
|
|
39
40
|
|
|
@@ -43,35 +44,15 @@ def _mask_key(key: str) -> str:
|
|
|
43
44
|
return key[:4] + "***" + key[-4:]
|
|
44
45
|
|
|
45
46
|
|
|
46
|
-
def _get_client(
|
|
47
|
-
"""Build a confsync client
|
|
48
|
-
the config's confsync: section (server_url / app)."""
|
|
47
|
+
def _get_client():
|
|
48
|
+
"""Build a confsync client from the shared confsync credentials."""
|
|
49
49
|
confsync, credentials = _import_confsync()
|
|
50
|
-
|
|
51
|
-
server_url_override, app = "", DEFAULT_APP
|
|
52
|
-
if os.path.exists(config_path):
|
|
53
|
-
cfg = load_config(config_path)
|
|
54
|
-
server_url_override = cfg.confsync.server_url
|
|
55
|
-
app = cfg.confsync.app or DEFAULT_APP
|
|
56
|
-
|
|
57
50
|
try:
|
|
58
51
|
client = credentials.load_client()
|
|
59
52
|
except confsync.ConfsyncError as e:
|
|
60
53
|
print(str(e))
|
|
61
54
|
sys.exit(1)
|
|
62
|
-
|
|
63
|
-
if server_url_override and server_url_override.rstrip("/") != client.server_url:
|
|
64
|
-
creds_path = credentials.get_credentials_path()
|
|
65
|
-
with open(creds_path) as f:
|
|
66
|
-
api_key = json.load(f)["api_key"]
|
|
67
|
-
client.close()
|
|
68
|
-
try:
|
|
69
|
-
client = confsync.ConfsyncClient(server_url_override, api_key)
|
|
70
|
-
except confsync.ConfsyncError as e:
|
|
71
|
-
print(str(e))
|
|
72
|
-
sys.exit(1)
|
|
73
|
-
|
|
74
|
-
return client, app
|
|
55
|
+
return client, DEFAULT_APP
|
|
75
56
|
|
|
76
57
|
|
|
77
58
|
def sync_push(config_path: str) -> None:
|
|
@@ -82,7 +63,7 @@ def sync_push(config_path: str) -> None:
|
|
|
82
63
|
with open(config_path, encoding="utf-8") as f:
|
|
83
64
|
content = f.read()
|
|
84
65
|
|
|
85
|
-
client, app = _get_client(
|
|
66
|
+
client, app = _get_client()
|
|
86
67
|
confsync, _ = _import_confsync()
|
|
87
68
|
with client:
|
|
88
69
|
try:
|
|
@@ -112,7 +93,7 @@ def sync_pull(config_path: str, dry_run: bool = False, full: bool = False) -> No
|
|
|
112
93
|
"""Pull the remote config: bootstrap, full replace (--full), or key merge."""
|
|
113
94
|
import yaml
|
|
114
95
|
|
|
115
|
-
client, app = _get_client(
|
|
96
|
+
client, app = _get_client()
|
|
116
97
|
confsync, _ = _import_confsync()
|
|
117
98
|
with client:
|
|
118
99
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|