fix(litellm): gate OAuth routes on real credentials

This commit is contained in:
Codex
2026-10-08 21:43:18 -07:00
parent 9a4020b7ec
commit 6473297555
4 changed files with 33 additions and 5 deletions
+4
View File
@@ -29,6 +29,8 @@ Google AI Studio key 尚未取得;latest 自动维护尚未实现。
只使用单个代理 worker,降低 OAuth refresh token 竞争的风险。 只使用单个代理 worker,降低 OAuth refresh token 竞争的风险。
所有配置从 Git 生成,不允许 UI 中的模型配置成为另一份权威。 所有配置从 Git 生成,不允许 UI 中的模型配置成为另一份权威。
未提供 key 或未完成 OAuth 登录的路线不会进入运行配置。 未提供 key 或未完成 OAuth 登录的路线不会进入运行配置。
OAuth 判定检查 auth.json 中 access_token,不仅检查文件存在性:
Codex 设备授权期间会先写入冷却占位文件,这不代表已经授权。
## Provider 与缓存 ## Provider 与缓存
@@ -100,6 +102,8 @@ bash oauth-login.sh xai
登录 helper 使用 host network 仅供本地 OAuth 回调,生产代理仍使用独立 bridge。 登录 helper 使用 host network 仅供本地 OAuth 回调,生产代理仍使用独立 bridge。
确认账号实际支持的模型 ID 后,在 `models.yaml` 的 `chatgpt`/`xai` 表添加别名。 确认账号实际支持的模型 ID 后,在 `models.yaml` 的 `chatgpt`/`xai` 表添加别名。
已预置的 OAuth 候选为 gpt-luna-latest/gpt-sol-latest/grok-latest,
只是先前 Hindsight 使用过的模型,不代表本代理独立账号已授权。
Codex 路线由 `chatgpt/` 访问,优先 `/v1/responses`; Codex 路线由 `chatgpt/` 访问,优先 `/v1/responses`;
xAI 路线使用 `xai/` 与 `use_xai_oauth: true`,不能设置 XAI API key 覆盖它。 xAI 路线使用 `xai/` 与 `use_xai_oauth: true`,不能设置 XAI API key 覆盖它。
新增 token 后需重启代理,运行时 refresh 会写回同一个持久卷。 新增 token 后需重启代理,运行时 refresh 会写回同一个持久卷。
+7 -3
View File
@@ -10,6 +10,10 @@ openrouter:
kimi-latest: moonshotai/kimi-k3 kimi-latest: moonshotai/kimi-k3
mimo-flash-latest: xiaomi/mimo-v2.6-flash mimo-flash-latest: xiaomi/mimo-v2.6-flash
google_ai_studio: {} google_ai_studio: {}
# OAuth 模型需完成独立登录,并根据账号实际模型目录确认后启用。 # 已有 Hindsight 部署用过的候选模型;本代理仍需独立授权与请求验收。
chatgpt: {} # renderer 在实际 token 出现前排除这些路线。
xai: {} chatgpt:
gpt-luna-latest: gpt-6-luna
gpt-sol-latest: gpt-6-sol
xai:
grok-latest: grok-4.7
+10 -1
View File
@@ -1,10 +1,19 @@
"""Render only credential-ready routes; credentials remain environment references.""" """Render only credential-ready routes; credentials remain environment references."""
import os import os
import json
from pathlib import Path from pathlib import Path
import yaml import yaml
def oauth_ready(path):
try:
record = json.loads(path.read_text())
return isinstance(record, dict) and bool(record.get('access_token'))
except (OSError, ValueError):
return False
def build_config(catalog, env, exists): def build_config(catalog, env, exists):
routes = [] routes = []
@@ -49,7 +58,7 @@ def build_config(catalog, env, exists):
if __name__ == '__main__': if __name__ == '__main__':
catalog = yaml.safe_load(Path('/etc/litellm/models.yaml').read_text()) catalog = yaml.safe_load(Path('/etc/litellm/models.yaml').read_text())
config = build_config(catalog, os.environ, Path.is_file) config = build_config(catalog, os.environ, oauth_ready)
output = Path('/tmp/litellm.yaml') output = Path('/tmp/litellm.yaml')
output.write_text(yaml.safe_dump(config, allow_unicode=True, sort_keys=False)) output.write_text(yaml.safe_dump(config, allow_unicode=True, sort_keys=False))
output.chmod(0o600) output.chmod(0o600)
+12 -1
View File
@@ -1,5 +1,7 @@
import unittest import unittest
from render_config import build_config from pathlib import Path
from tempfile import TemporaryDirectory
from render_config import build_config, oauth_ready
class ConfigTests(unittest.TestCase): class ConfigTests(unittest.TestCase):
@@ -33,6 +35,15 @@ class ConfigTests(unittest.TestCase):
self.assertTrue(routes[0]['litellm_params']['use_xai_oauth']) self.assertTrue(routes[0]['litellm_params']['use_xai_oauth'])
self.assertNotIn('api_key', routes[0]['litellm_params']) self.assertNotIn('api_key', routes[0]['litellm_params'])
def test_pending_oauth_is_not_ready(self):
with TemporaryDirectory() as folder:
path = Path(folder) / 'auth.json'
self.assertFalse(oauth_ready(path))
path.write_text('{"device_code_requested_at": 1}')
self.assertFalse(oauth_ready(path))
path.write_text('{"access_token": "fixture-only"}')
self.assertTrue(oauth_ready(path))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()