Skip to content

Commit 0037b58

Browse files
wehosHongzhi Wenclaude
authored
fix(config): agent URL 按线路路由(国内 lanlan.app / 国际 www.lanlan.app) (#1491)
* fix(config): agent URL 按线路路由(国内 lanlan.app / 国际 www.lanlan.app) 原本 agent URL 无条件把 lanlan.tech 改写为 lanlan.app,国内外都落到 www.lanlan.app。改为抽出 _normalize_agent_url:统一 tech→app 后,国际 保留 www 前缀,国内剥掉 www 走就近节点;GeoIP 不确定时按国内处理,与 _adjust_free_api_url 口径一致。不含 lanlan 域的自定义 URL 原样返回。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(config): 注释 _normalize_agent_url 的 GeoIP 异常降级语义 回应 code-quality 对空 except 的提醒:补充注释说明探测异常时保留国际 形态作为安全默认,行为不变但可追踪。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(config): 补 bare host 用例固化 agent URL 不补 www 契约 回应 CodeRabbit nitpick:bare lanlan.tech 输入(仅可能来自自定义 URL) 两区都落到 bare lanlan.app,显式记录尊重用户 host、不强加 www 的行为。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(config): agent URL 改写改为按 host 判定,不做字符串级 replace 回应 Codex/CodeRabbit:原裸 replace('www.lanlan.app','lanlan.app') 会 误改 path/query 里出现的同名子串(如自定义代理的 upstream 参数),且比 同文件 _adjust_free_api_url 锚定 /tts 的 www-strip 更松。改为 urlparse 后仅当 host 恰为 lanlan.tech/www.lanlan.tech/lanlan.app/www.lanlan.app 时重写 netloc,其余域一律原样透传,保留端口。 新增用例:query/path 含 lanlan 字样的自定义代理不被改写、evil-lanlan.app 后缀不命中、端口保留。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(config): agent URL 按组件重建 netloc,不动 userinfo 回应 Codex/CodeRabbit:原 netloc.replace(hostname,...) 在 netloc 含 user:pass@ 且凭证里出现 host 同名串时会先替到 userinfo(改坏凭证、host 没动),大小写不一致时也替不到。改为按 username/password/port 组件重建 netloc,host 用规范化后的小写值直接拼,两类场景都稳。 新增用例:user:pass@(凭证含 host 同名串)只改 host。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(config): agent URL 端口非法时原样返回,不拖垮 config 加载 回应 Codex P2:urlparse 不校验端口,.port 访问才对 :abc / 越界端口抛 ValueError。该访问在 GeoIP try 之外,会让 get_core_config() 整体崩溃 (旧字符串 replace 不会)。给 .port 访问加 ValueError guard,非法端口 原样返回交给下游处理。 新增用例:端口非数字 / 越界 → passthrough。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(config): agent URL 保留空但存在的 userinfo 分量 回应 Codex P2:截断式 if parsed.username/password 把空字符串当缺失, 会丢掉 https://:token@host 的整个 :token@(bearer 式代理常用),改坏 认证语义。改用 is not None 区分「分量为空但存在」与「不存在」。 新增用例::token@(空用户名)/ user:@(空密码)均原样保留。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(config): agent URL 保留显式 :0 端口 回应 Codex P2:port 三元用 if parsed.port 截断,端口 0 合法但 falsy, 会把显式 :0 丢成默认端口。与 userinfo 同类,改用 is not None。 新增用例::0 端口保留。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert(config): agent URL 改回简单字符串替换,去掉过度工程化的 urlparse lanlan.tech / lanlan.app 是项目自有域名,AGENT_MODEL_URL 要么是写死的 free 默认,要么是用户自填的非 lanlan 服务 URL。bot review 提的内嵌凭证 / :0 端口 / :abc 畸形端口 / query 里带 lanlan 字样等场景对这两个域名根本 不会出现,为它们引入 urlparse + netloc 组件重建属于过度工程化。 回到两行 replace(helper 仅为两个调用点复用区域逻辑),测试同步精简。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(config): 给 _normalize_agent_url 的 except 补降级语义注释 revert 时误删了解释注释。空 except 零注释本身不好(与 bot 提不提无关): 补一句说明仅 _check_non_mainland 可能抛、探测失败时保留国际形态。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Hongzhi Wen <cartabio.coder1@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4b953a7 commit 0037b58

2 files changed

Lines changed: 65 additions & 2 deletions

File tree

tests/unit/test_api_config_manager.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,49 @@ def test_agent_uses_dedicated_fields_but_not_custom_when_toggle_off(self, config
625625
'Agent URL should have lanlan.app normalization applied'
626626

627627

628+
# ---------------------------------------------------------------------------
629+
# 7b. Agent URL region routing: 国内 lanlan.app / 国际 www.lanlan.app
630+
# ---------------------------------------------------------------------------
631+
class TestAgentUrlRegionRouting:
632+
633+
@pytest.mark.unit
634+
@pytest.mark.parametrize(
635+
('non_mainland', 'url_in', 'expected'),
636+
[
637+
# 国际:保留 www 前缀
638+
(True, 'https://www.lanlan.tech/text/v1', 'https://www.lanlan.app/text/v1'),
639+
# 国内:剥掉 www
640+
(False, 'https://www.lanlan.tech/text/v1', 'https://lanlan.app/text/v1'),
641+
# GeoIP 不确定(按国内处理):同样剥 www
642+
(None, 'https://www.lanlan.tech/text/v1', 'https://lanlan.app/text/v1'),
643+
# 已经是 www.lanlan.app 的国内输入:仍剥 www(幂等)
644+
(False, 'https://www.lanlan.app/text/v1', 'https://lanlan.app/text/v1'),
645+
# 国际下 www.lanlan.app 保持不变
646+
(True, 'https://www.lanlan.app/text/v1', 'https://www.lanlan.app/text/v1'),
647+
# bare host 输入(无 www,仅可能来自自定义 URL):尊重原样,不补 www,
648+
# 两区都落到 bare lanlan.app(仅做 tech→app)
649+
(True, 'https://lanlan.tech/text/v1', 'https://lanlan.app/text/v1'),
650+
(False, 'https://lanlan.tech/text/v1', 'https://lanlan.app/text/v1'),
651+
],
652+
)
653+
def test_normalize_agent_url_by_region(self, config_manager, non_mainland, url_in, expected):
654+
config_manager._check_non_mainland = lambda: non_mainland
655+
assert config_manager._normalize_agent_url(url_in) == expected
656+
657+
@pytest.mark.unit
658+
@pytest.mark.parametrize('non_mainland', [True, False, None])
659+
def test_normalize_agent_url_custom_url_untouched(self, config_manager, non_mainland):
660+
"""不含 lanlan 域的自定义 URL 原样返回,不受线路影响。"""
661+
config_manager._check_non_mainland = lambda: non_mainland
662+
custom = 'https://api.openai.com/v1'
663+
assert config_manager._normalize_agent_url(custom) == custom
664+
665+
@pytest.mark.unit
666+
def test_normalize_agent_url_non_string_passthrough(self, config_manager):
667+
config_manager._check_non_mainland = lambda: True
668+
assert config_manager._normalize_agent_url(None) is None
669+
670+
628671
# ---------------------------------------------------------------------------
629672
# 8. MiniMax / Qwen voice clone key resolution
630673
# ---------------------------------------------------------------------------

utils/config_manager.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2971,6 +2971,26 @@ def _adjust_free_api_url(self, url: str, is_free: bool) -> str:
29712971

29722972
return url
29732973

2974+
def _normalize_agent_url(self, url: str) -> str:
2975+
"""Agent 模型始终走 lanlan.app(国际 API),统一 lanlan.tech → lanlan.app;
2976+
国际保留 www(www.lanlan.app),国内剥掉 www(lanlan.app)走就近节点。
2977+
2978+
lanlan.tech / lanlan.app 是项目自有域名,AGENT_MODEL_URL 要么是写死的
2979+
free 默认(https://www.lanlan.tech/text/v1),要么是用户自填的其它服务
2980+
URL(不含这两个域,replace 自然 no-op),所以直接字符串替换即可。
2981+
"""
2982+
if not isinstance(url, str):
2983+
return url
2984+
url = url.replace('lanlan.tech', 'lanlan.app')
2985+
try:
2986+
if not self._check_non_mainland():
2987+
url = url.replace('www.lanlan.app', 'lanlan.app')
2988+
except Exception:
2989+
# 仅 _check_non_mainland 可能抛(GeoIP 探测)。探测失败时不剥 www,
2990+
# 保留国际形态作安全默认;线路探测不该阻断 URL 推导。
2991+
pass
2992+
return url
2993+
29742994
@staticmethod
29752995
def _derive_livestream_url(original_url: str, prefix: str) -> str:
29762996
"""从 livestream server_prefix 派生 lanlan.tech URL 的等价地址。
@@ -3280,7 +3300,7 @@ def _fb(provider: str) -> str:
32803300
# agent api 默认跟随辅助 API 的 agent_model,缺失时回退到 VISION_MODEL
32813301
config['AGENT_MODEL'] = config.get('AGENT_MODEL') or config.get('VISION_MODEL', '')
32823302
config['AGENT_MODEL_URL'] = config.get('AGENT_MODEL_URL') or config.get('VISION_MODEL_URL', '') or config.get('OPENROUTER_URL', '')
3283-
config['AGENT_MODEL_URL'] = config['AGENT_MODEL_URL'].replace('lanlan.tech', 'lanlan.app') # TODO: 先放这里
3303+
config['AGENT_MODEL_URL'] = self._normalize_agent_url(config['AGENT_MODEL_URL'])
32843304

32853305
key_field = assist_api_key_fields.get(assist_api_value)
32863306
derived_key = ''
@@ -3463,7 +3483,7 @@ def _resolve_follow_model_url(prefix: str, provider: str) -> str:
34633483

34643484
# Agent model always uses international API regardless of region
34653485
if isinstance(config.get('AGENT_MODEL_URL'), str):
3466-
config['AGENT_MODEL_URL'] = config['AGENT_MODEL_URL'].replace('lanlan.tech', 'lanlan.app')
3486+
config['AGENT_MODEL_URL'] = self._normalize_agent_url(config['AGENT_MODEL_URL'])
34673487

34683488
return config
34693489

0 commit comments

Comments
 (0)