From e9c8aec43e6d970cbacb32160b3ea8a0171e187a Mon Sep 17 00:00:00 2001 From: fanziqing Date: Fri, 14 Aug 2026 16:56:16 +0800 Subject: [PATCH] feat: verify Ark credentials via Responses --- coworker/providers/registry.py | 24 ++++++++++++-- tests/test_provider_verify.py | 58 ++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/coworker/providers/registry.py b/coworker/providers/registry.py index 01445564..04ed994e 100644 --- a/coworker/providers/registry.py +++ b/coworker/providers/registry.py @@ -882,9 +882,11 @@ def verify_provider_key( fields: Optional[dict[str, Any]] = None, timeout: float = 10.0, ) -> dict[str, Any]: - """Validate a provider's credentials with one cheap, read-only call (list models) — the same - pattern connectors use to validate tokens. Transient: callers pass the key directly so a user - can Test before saving. Never raises; returns {ok, error?}. Multi-field cloud providers + """Validate a provider's credentials with one cheap call — usually list models. + + Ark's Responses-compatible data plane does not document a `/models` probe, so its Test button + sends a non-persisted one-token Responses request instead. Callers pass the key directly so a + user can Test before saving. Never raises; returns {ok, error?}. Multi-field cloud providers (Bedrock, Vertex) take their whole form via `fields`; everyone else uses api_key/base_url. """ import httpx @@ -911,6 +913,22 @@ def verify_provider_key( elif name == "ollama": base = _normalize_ollama_url(base_url) resp = httpx.get(base.rstrip("/") + "/models", timeout=timeout) + elif name in ("ark", "ark-agent-plan-cn"): + default_base = next( + (f.default for f in d.fields if f.key == "base_url" and f.default), "" + ) + base = (base_url or "").strip().rstrip("/") or default_base.rstrip("/") + resp = httpx.post( + base + "/responses", + headers={"Authorization": f"Bearer {key}"}, + json={ + "model": d.recommended_model, + "input": "Reply with OK.", + "max_output_tokens": 1, + "store": False, + }, + timeout=timeout, + ) else: # openai + any OpenAI-compatible endpoint (Azure, OpenRouter, vendors, vLLM…) default_base = next( (f.default for f in d.fields if f.key == "base_url" and f.default), "" diff --git a/tests/test_provider_verify.py b/tests/test_provider_verify.py index 8da7fe76..a6e553af 100644 --- a/tests/test_provider_verify.py +++ b/tests/test_provider_verify.py @@ -41,6 +41,18 @@ def _patch_get(monkeypatch, status=200, capture=None, raise_exc=None): monkeypatch.setattr("httpx.get", fake_get) +def _patch_post(monkeypatch, status=200, capture=None, raise_exc=None): + def fake_post(url, **kwargs): + if capture is not None: + capture["url"] = url + capture.update(kwargs) + if raise_exc is not None: + raise raise_exc + return SimpleNamespace(status_code=status) + + monkeypatch.setattr("httpx.post", fake_post) + + def test_verify_openai_ok(monkeypatch): cap: dict = {} _patch_get(monkeypatch, status=200, capture=cap) @@ -91,6 +103,52 @@ def test_verify_ollama_uses_v1_models_no_key(monkeypatch): assert "headers" not in cap # keyless +@pytest.mark.parametrize( + "name,base_url,model", + [ + ( + "ark", + "https://ark.ap-southeast.bytepluses.com/api/v3", + "dola-seed-evolving-latest-version", + ), + ( + "ark-agent-plan-cn", + "https://ark.cn-beijing.volces.com/api/plan/v3", + "doubao-seed-evolving", + ), + ], +) +def test_verify_ark_uses_non_persisted_responses_probe( + monkeypatch, name, base_url, model +): + """Reverse-verified probe: the captured fixture must be non-empty and provider-specific.""" + cap: dict = {} + _patch_post(monkeypatch, status=200, capture=cap) + + assert verify_provider_key(name, api_key="ark-key") == {"ok": True} + assert cap["url"] == base_url + "/responses" + assert cap["headers"]["Authorization"] == "Bearer ark-key" + assert cap["json"] == { + "model": model, + "input": "Reply with OK.", + "max_output_tokens": 1, + "store": False, + } + + +def test_verify_ark_profile_endpoint_override(monkeypatch): + cap: dict = {} + _patch_post(monkeypatch, status=200, capture=cap) + + verify_provider_key( + "ark", + api_key="ark-key", + base_url="https://gateway.example/ark/v3/", + ) + + assert cap["url"] == "https://gateway.example/ark/v3/responses" + + def test_verify_network_error_is_clean(monkeypatch): _patch_get(monkeypatch, raise_exc=ConnectionError("boom")) res = verify_provider_key("openai", api_key="sk-x")