diff --git a/README.md b/README.md index f0db6cc..d93c2ef 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ PR 自动审查闸门守护进程(第 4 组件,experimental):常驻轮 ### Model ID 格式(canonical) -**canonical model id = `~/.zcode/v2/config.json` 里 `models` 的 key 原样**(如 `GLM-5.3`),**不加 provider 前缀**。`shared/credentials.py`、MCP server、ACP bridge 三处统一用原始 id。实测(0.16.1 时代)`zai/GLM-5.2` 前缀形式也兼容,但非 canonical,本项目不使用。 +**canonical model id = `~/.zcode/v2/config.json` 里 `models` 的 key 原样**(如 `GLM-5.3`),**不加 provider 前缀**。`shared/credentials.py`、MCP server、ACP bridge、agent-help 四处统一用原始 id。实测(0.16.1 时代)`zai/GLM-5.2` 前缀形式也兼容,但非 canonical,本项目不使用。 模型面现状(0.16.5 实测):当前 enabled provider(`builtin:zai-coding-plan`)的 models 为 `GLM-5.3` / `GLM-5.3-Flash` / `GLM-5-Turbo`。 @@ -391,7 +391,7 @@ zcode-open-bridge/ └── README.md ``` -每个组件都是**单文件、零依赖**,复制一个文件即可独立运行(`shared/credentials.py` 的逻辑已内嵌到 mcp-server/acp-bridge)。 +每个组件都是**单文件、零依赖**,复制一个文件即可独立运行(`shared/credentials.py` 的逻辑已内嵌到 mcp-server/acp-bridge/agent-help)。 ## 开发 diff --git a/packages/acp-bridge/zcode-acp-bridge b/packages/acp-bridge/zcode-acp-bridge index 31d8b26..88edf75 100755 --- a/packages/acp-bridge/zcode-acp-bridge +++ b/packages/acp-bridge/zcode-acp-bridge @@ -2,7 +2,7 @@ """ zcode-acp-bridge — 把 headless ZCode 桥接为 ACP (Agent Client Protocol) Agent -让 Zed / JetBrains 等 ACP 兼容编辑器能通过标准 ACP 协议调用 zcode (GLM-5.2)。 +让 Zed / JetBrains 等 ACP 兼容编辑器能通过标准 ACP 协议调用 zcode (GLM-5.3)。 工作原理 (双模式自动选择): ACP Client (编辑器) zcode-acp-bridge zcode app-server @@ -91,7 +91,7 @@ from urllib.parse import urlparse # 配置 # ============================================================ ACP_PROTOCOL_VERSION = 1 -BRIDGE_INFO = {"name": "zcode-acp-bridge", "title": "ZCode (GLM-5.2)", "version": "1.0.0"} +BRIDGE_INFO = {"name": "zcode-acp-bridge", "title": "ZCode (GLM-5.3)", "version": "1.0.0"} ZCODE_BIN = os.environ.get("ZCODE_BIN", "zcode") ZCODE_CREDS_PATH = Path.home() / ".zcode" / "v2" / "config.json" @@ -140,7 +140,7 @@ def load_zcode_credentials(): opts = p.get("options", {}) models = p.get("models", {}) return { - "ZCODE_MODEL": next(iter(models)) if models else "GLM-5.2", + "ZCODE_MODEL": next(iter(models)) if models else "GLM-5.3", "ZCODE_BASE_URL": opts.get("baseURL", ""), "ANTHROPIC_API_KEY": opts.get("apiKey", ""), } diff --git a/packages/agent-help/zcode-agent-help b/packages/agent-help/zcode-agent-help index f87888e..8110c8f 100755 --- a/packages/agent-help/zcode-agent-help +++ b/packages/agent-help/zcode-agent-help @@ -30,7 +30,7 @@ from urllib.parse import urlparse ZCODE_BIN = os.environ.get("ZCODE_BIN", "zcode") -# 模型 ID 的 canonical 格式: config 里 models 的 key 原样 (如 GLM-5.2), 不加 provider 前缀。 +# 模型 ID 的 canonical 格式: config 里 models 的 key 原样 (如 GLM-5.3), 不加 provider 前缀。 # 实测 "zai/GLM-5.2" 也兼容, 但与 shared/credentials.py 权威实现不一致, 故统一用原始 id。 ZCODE_CREDS_PATH = Path.home() / ".zcode" / "v2" / "config.json" @@ -49,7 +49,7 @@ def _load_creds_internal(config_path=None): if p.get("enabled"): opts = p.get("options", {}) models = p.get("models", {}) - model_id = next(iter(models)) if models else "GLM-5.2" + model_id = next(iter(models)) if models else "GLM-5.3" return { "ZCODE_MODEL": model_id, "ZCODE_BASE_URL": opts.get("baseURL", ""), @@ -199,7 +199,7 @@ NATIVE_CLI = { "name": "原生 Headless CLI", "binary": "zcode", "env_required": { - "ZCODE_MODEL": {"description": "模型 ID, 如 GLM-5.2", "example": "GLM-5.2"}, + "ZCODE_MODEL": {"description": "模型 ID, 如 GLM-5.3", "example": "GLM-5.3"}, "ZCODE_BASE_URL": {"description": "API 端点", "example": "https://api.z.ai/api/anthropic"}, "ANTHROPIC_API_KEY": {"description": "API 密钥 (provider kind 为 anthropic)", "example": ""}, }, diff --git a/packages/mcp-server/zcode-mcp-server b/packages/mcp-server/zcode-mcp-server index 917b8af..ef7b91b 100755 --- a/packages/mcp-server/zcode-mcp-server +++ b/packages/mcp-server/zcode-mcp-server @@ -84,7 +84,7 @@ def load_zcode_credentials(): if p.get("enabled"): opts = p.get("options", {}) models = p.get("models", {}) - model_id = next(iter(models)) if models else "GLM-5.2" + model_id = next(iter(models)) if models else "GLM-5.3" return { "ZCODE_MODEL": model_id, "ZCODE_BASE_URL": opts.get("baseURL", ""), diff --git a/shared/credentials.py b/shared/credentials.py index bb0906b..d97274e 100644 --- a/shared/credentials.py +++ b/shared/credentials.py @@ -37,7 +37,7 @@ def load_zcode_credentials(config_path=None): """从 ~/.zcode/v2/config.json 动态读取凭证。 读取第一个 enabled 的 provider, 返回环境变量 dict: - ZCODE_MODEL: 模型 ID (如 GLM-5.2) + ZCODE_MODEL: 模型 ID (如 GLM-5.3) ZCODE_BASE_URL: API 端点 ANTHROPIC_API_KEY: API 密钥 (provider kind 为 anthropic) @@ -56,7 +56,7 @@ def load_zcode_credentials(config_path=None): if p.get("enabled"): opts = p.get("options", {}) models = p.get("models", {}) - model_id = next(iter(models)) if models else "GLM-5.2" + model_id = next(iter(models)) if models else "GLM-5.3" return { "ZCODE_MODEL": model_id, "ZCODE_BASE_URL": opts.get("baseURL", ""), diff --git a/skills/zcode-bridge-guide/SKILL.md b/skills/zcode-bridge-guide/SKILL.md index e567c5a..0c6beca 100644 --- a/skills/zcode-bridge-guide/SKILL.md +++ b/skills/zcode-bridge-guide/SKILL.md @@ -46,7 +46,7 @@ c=json.load(open('$cfg')) for k,v in c['provider'].items(): if v.get('enabled'): o=v['options'] - print(f'export ZCODE_MODEL=\"{next(iter(v.get(\"models\",{}))) or \"GLM-5.2\"}\"') + print(f'export ZCODE_MODEL=\"{next(iter(v.get(\"models\",{}))) or \"GLM-5.3\"}\"') print(f'export ZCODE_BASE_URL=\"{o.get(\"baseURL\",\"\")}\"') print(f'export ANTHROPIC_API_KEY=\"{o.get(\"apiKey\",\"\")}\"') break @@ -55,7 +55,7 @@ for k,v in c['provider'].items(): } ``` -> ACP bridge 和 MCP server 内部已实现同样的凭证读取逻辑(`shared/credentials.py`),无需额外配置。 +> ACP bridge、MCP server 和 agent-help 内部已实现同样的凭证读取逻辑(`shared/credentials.py`),无需额外配置。 ### ⚠️ 非交互 shell 坑(Codex/agent 编排注意) @@ -129,9 +129,9 @@ cfg = json.loads((pathlib.Path.home() / ".zcode/v2/config.json").read_text()) for pid, p in cfg.get("provider", {}).items(): if p.get("enabled"): o = p["options"] - # canonical model id = config 里 models 的 key 原样 (如 GLM-5.2), 不加 provider 前缀。 + # canonical model id = config 里 models 的 key 原样 (如 GLM-5.3), 不加 provider 前缀。 # 实测 "zai/GLM-5.2" 也兼容, 但与本项目的权威实现 (shared/credentials.py) 不一致, 故统一用原始 id。 - m = "GLM-5.2" if "GLM-5.2" in p.get("models", {}) else next(iter(p.get("models", {})), "") + m = next(iter(p.get("models", {})), "") print(m, o.get("baseURL", ""), o.get("apiKey", "")) break PY diff --git a/tests/test_credentials.py b/tests/test_credentials.py index 14b84fa..f839535 100644 --- a/tests/test_credentials.py +++ b/tests/test_credentials.py @@ -6,7 +6,7 @@ C0 正常读取: 返回第一个 enabled provider 的 model/baseURL/apiKey C1 model id 是原始格式 (config 里 models 的 key 原样, 如 GLM-5.2, 不加 zai/ 前缀) - C2 models 为空 → 兜底 GLM-5.2 + C2 models 为空 → 兜底 GLM-5.3 C3 无 enabled provider → 返回 {} C4 config 文件缺失 → 返回 {} (不崩) C5 config JSON 损坏 → 返回 {} (不崩) @@ -105,9 +105,9 @@ def test_c1b_custom_model_id(self): # ---------- C2: models 为空兜底 ---------- def test_c2_empty_models_fallback(self): - """C2: models 为空 → 兜底 GLM-5.2""" + """C2: models 为空 → 兜底 GLM-5.3""" c = self._creds(_config_with_provider(models={})) - self.assertEqual(c["ZCODE_MODEL"], "GLM-5.2") + self.assertEqual(c["ZCODE_MODEL"], "GLM-5.3") # ---------- C3: 无 enabled provider ---------- def test_c3_no_enabled_provider(self): @@ -335,8 +335,14 @@ def _isolated_env(test_case, home, **env): def _write_isolated_config(home, enabled_url="https://api.z.ai/api/anthropic", - stale_url="https://zcode.z.ai/api/v1/zcode-plan/anthropic"): - """在隔离 HOME 里写两 provider 的 config (enabled + 一个 disabled 的 stale), 返回路径。""" + stale_url="https://zcode.z.ai/api/v1/zcode-plan/anthropic", + models=None): + """在隔离 HOME 里写两 provider 的 config (enabled + 一个 disabled 的 stale), 返回路径。 + + models: 可选, 覆盖两个 provider 的 models (默认合成数据 GLM-5.2; C13 传 {} 走兜底分支)。 + """ + if models is None: + models = {"GLM-5.2": {}} cfg_dir = os.path.join(home, ".zcode", "v2") os.makedirs(cfg_dir, exist_ok=True) cfg_path = os.path.join(cfg_dir, "config.json") @@ -346,12 +352,12 @@ def _write_isolated_config(home, enabled_url="https://api.z.ai/api/anthropic", "builtin:zai-coding-plan": { "enabled": True, "options": {"baseURL": enabled_url, "apiKey": "sk-good-key-123456"}, - "models": {"GLM-5.2": {}}, + "models": models, }, "builtin:zai-start-plan": { "enabled": False, "options": {"baseURL": stale_url, "apiKey": "jwt-stale"}, - "models": {"GLM-5.2": {}}, + "models": models, }, } }, f) @@ -565,5 +571,52 @@ def test_c12j_stale_env_diagnosed(self): self.assertIn("🚫 残留", out.getvalue()) +# ============================================================ +# C13: models={} 兜底分支对拍 (双审遗漏项) +# C11/C12 的对拍 fixture models 均非空, 兜底分支在三副本上无自动化对拍, +# 四处兜底字面量此前只靠手工 grep 保证一致。本用例在 models={} fixture 上 +# 断言权威版与三副本的兜底 ZCODE_MODEL 相等, 且等于权威版锚点字面量 — +# 未来模型升级只需改 shared/credentials.py + 此处锚点, 漏改任一副本即测试红。 +# ============================================================ +ACP_BRIDGE_PATH = os.path.join( + os.path.dirname(__file__), "..", "packages", "acp-bridge", "zcode-acp-bridge" +) + + +class TestEmptyModelsFallbackParity(unittest.TestCase): + """C13: models={} 时四处兜底一致 (权威版锚点 + 三内嵌副本对拍)。""" + + @classmethod + def setUpClass(cls): + cls.mcp = _load_single_file_module(MCP_SERVER_PATH, "zcode_mcp_server_c13") + cls.acp = _load_single_file_module(ACP_BRIDGE_PATH, "zcode_acp_bridge_c13") + cls.ah = _load_single_file_module(AGENT_HELP_PATH, "zcode_agent_help_c13") + + def test_c13_empty_models_fallback_parity(self): + """C13: models={} → 权威版/三副本兜底 ZCODE_MODEL 相等, 且权威版 == "GLM-5.3\"""" + with tempfile.TemporaryDirectory() as home: + cfg_path = _write_isolated_config(home, models={}) # 空 models → 兜底分支 + _isolated_env(self, home) # mcp 副本用 Path.home() 定位 config + ref = load_zcode_credentials(config_path=cfg_path) + mcp_creds = self.mcp.load_zcode_credentials() + # acp 副本的 config 路径是模块级常量 (exec 时绑定), 指到临时 config + # (与 test_app_server_methods.RV10 的 patch 方式一致) + orig_path = self.acp.ZCODE_CREDS_PATH + try: + self.acp.ZCODE_CREDS_PATH = cfg_path + acp_creds = self.acp.load_zcode_credentials() + finally: + self.acp.ZCODE_CREDS_PATH = orig_path + ah_creds = self.ah._load_creds_internal(cfg_path) + # 权威锚点: 兜底值本身 (与 shared/credentials.py 的字面量同步维护) + self.assertEqual(ref.get("ZCODE_MODEL"), "GLM-5.3", + "权威版兜底应为 GLM-5.3 (当前默认模型)") + # 三副本不各自硬编码断言, 只对拍权威版 — 漏改任一副本即在此暴露 + for name, creds in (("mcp-server", mcp_creds), ("acp-bridge", acp_creds), + ("agent-help", ah_creds)): + self.assertEqual(creds.get("ZCODE_MODEL"), ref.get("ZCODE_MODEL"), + f"models 为空时 {name} 副本兜底与权威版不一致 (四处漏改?)") + + if __name__ == "__main__": unittest.main(verbosity=2)