v3 M3: z.ai (GLM) provider — Anthropic-compatible poll from a pasted API key
ZaiProvider replaces the zai stub. It reaches z.ai's Anthropic-compatible endpoint (default https://api.z.ai/api/anthropic) exactly like Claude Code with ANTHROPIC_BASE_URL pointed at z.ai: a /v1/messages POST with a Bearer key. The key + base URL come from the config the control panel's z.ai field writes (providers.zai.base_url/.api_key) — no OAuth, no local files. Two z.ai-specific cares: (1) static API key, no self-refresh; (2) the GLM Coding Plan meters by prompts, so a 60s poll would drain quota — the provider self-throttles to one network call per poll_interval_s (default 15 min) and serves a cached status in between. Rate-limit parsing assumes z.ai proxies Anthropic's unified headers (5h->s/sr, 7d->w/wr); if a real response lacks them it still reports connected and logs the limit-ish headers it did return, so the mapping can be finalized against a live key. tools/probe_zai.py dumps a real z.ai response's headers (key via env, never printed) to pin the exact header names. Graceful without a key: selecting z.ai shows the blue theme + "no key", never crashes the loop. Registry: all three providers now real; unknown ids fall back to StubProvider. +7 z.ai tests (mocked transport: header mapping, auth, throttle, fallback); 120 passed, 2 Linux-only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,8 @@ import json
|
||||
|
||||
from daemon import config
|
||||
from daemon.providers import (
|
||||
get_provider, AnthropicProvider, OpenAICodexProvider, StubProvider, ProviderStatus)
|
||||
get_provider, AnthropicProvider, OpenAICodexProvider, ZaiProvider,
|
||||
StubProvider, ProviderStatus)
|
||||
|
||||
|
||||
def _run(coro):
|
||||
@@ -70,17 +71,19 @@ def test_registry_types():
|
||||
a = get_provider("anthropic")
|
||||
assert isinstance(a, AnthropicProvider) and a.id == "anthropic"
|
||||
o = get_provider("openai")
|
||||
assert isinstance(o, OpenAICodexProvider)
|
||||
assert o.id == "openai" and o.accent == "10a37f"
|
||||
stub = get_provider("zai") # z.ai is still a stub until M3
|
||||
assert isinstance(stub, StubProvider)
|
||||
assert stub.id == "zai" and stub.accent == "3859ff"
|
||||
assert isinstance(o, OpenAICodexProvider) and o.id == "openai" and o.accent == "10a37f"
|
||||
z = get_provider("zai")
|
||||
assert isinstance(z, ZaiProvider) and z.id == "zai" and z.accent == "3859ff"
|
||||
stub = get_provider("mystery") # unknown id → safe placeholder, never crashes
|
||||
assert isinstance(stub, StubProvider) and stub.id == "mystery"
|
||||
|
||||
|
||||
def test_stub_provider_poll_is_safe():
|
||||
def test_zai_poll_without_key_is_safe(tmp_path, monkeypatch):
|
||||
# No api_key configured => "no key", no network call.
|
||||
_write(tmp_path, monkeypatch, {"version": 2, "providers": {"zai": {"enabled": True}}})
|
||||
st = _run(get_provider("zai").poll())
|
||||
assert isinstance(st, ProviderStatus)
|
||||
assert st.ok is False
|
||||
assert st.ok is False and st.st == "no key"
|
||||
|
||||
|
||||
def test_provider_status_payload_keys():
|
||||
|
||||
Reference in New Issue
Block a user