Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`。

Expand Down Expand Up @@ -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)。

## 开发

Expand Down
6 changes: 3 additions & 3 deletions packages/acp-bridge/zcode-acp-bridge
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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", ""),
}
Expand Down
6 changes: 3 additions & 3 deletions packages/agent-help/zcode-agent-help
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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", ""),
Expand Down Expand Up @@ -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": "<your-key>"},
},
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/zcode-mcp-server
Original file line number Diff line number Diff line change
Expand Up @@ -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", ""),
Expand Down
4 changes: 2 additions & 2 deletions shared/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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", ""),
Expand Down
8 changes: 4 additions & 4 deletions skills/zcode-bridge-guide/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -55,7 +55,7 @@ for k,v in c['provider'].items():
}
```

> ACP bridgeMCP server 内部已实现同样的凭证读取逻辑(`shared/credentials.py`),无需额外配置。
> ACP bridgeMCP server 和 agent-help 内部已实现同样的凭证读取逻辑(`shared/credentials.py`),无需额外配置。

### ⚠️ 非交互 shell 坑(Codex/agent 编排注意)

Expand Down Expand Up @@ -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
Expand Down
67 changes: 60 additions & 7 deletions tests/test_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 损坏 → 返回 {} (不崩)
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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")
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Loading