feat: register BytePlus and Volcengine Ark providers

This commit is contained in:
fanziqing
2026-08-14 16:54:48 +08:00
parent d0103947c3
commit 2cb6e36b24
2 changed files with 147 additions and 0 deletions
+79
View File
@@ -210,6 +210,29 @@ def _openai_compat(vendor: str, default_base_url: str, env_key: Optional[str] =
return build
def _openai_responses_compat(
vendor: str, default_base_url: str, env_key: Optional[str] = None
):
"""Builder factory for vendors that explicitly implement the OpenAI Responses API.
Credentials stay isolated to the vendor's own profile/environment variable, matching the
Chat Completions compat path above. In particular, an OpenAI key is never sent to Ark.
"""
def build(profile: dict[str, Any], secrets: Any) -> ProviderClient:
base_url = ((profile or {}).get("base_url") or "").strip() or default_base_url
api_key = ((profile or {}).get("api_key") or "").strip() or (
os.environ.get(env_key, "").strip() if env_key else ""
)
if not api_key:
raise RuntimeError(
f"No {vendor} API key configured — add it in Settings ▸ Models."
)
return OpenAIResponsesProvider(api_key=api_key, base_url=base_url)
return build
def _compat(
name: str,
title: str,
@@ -248,6 +271,43 @@ def _compat(
)
def _responses_compat(
name: str,
title: str,
*,
base_url: str,
recommended_model: str,
env_key: str,
endpoint_help: str = "",
) -> ProviderDescriptor:
"""Descriptor for a vendor exposing the OpenAI Responses API."""
return ProviderDescriptor(
name=name,
title=title,
needs_key=True,
fields=[
ProviderField(
"api_key",
f"{title} API key",
secret=True,
),
ProviderField(
"base_url",
"Endpoint",
required=False,
default=base_url,
placeholder=base_url,
help=endpoint_help
or f"Prefilled with {title}'s official Responses endpoint.",
),
],
build=_openai_responses_compat(title, base_url, env_key),
recommended_model=recommended_model,
env_key=env_key,
blurb=f"Uses {title}'s OpenAI-compatible Responses API — the endpoint is prefilled, just add your key.",
)
DESCRIPTORS: list[ProviderDescriptor] = [
ProviderDescriptor(
name="openai",
@@ -462,6 +522,25 @@ DESCRIPTORS: list[ProviderDescriptor] = [
blurb="Runs models inside your own Google Cloud project. Gemini and Claude use "
"their native APIs; open-weight models go through the Vertex MaaS endpoint.",
),
# Ark has two intentionally separate provider identities. BytePlus pay-as-you-go and
# Volcengine Agent Plan use different regions, endpoints, credentials, and model catalogs;
# combining them would let one provider profile route a model to the wrong service.
_responses_compat(
"ark",
"BytePlus Ark",
base_url="https://ark.ap-southeast.bytepluses.com/api/v3",
recommended_model="dola-seed-evolving-latest-version",
env_key="ARK_API_KEY",
endpoint_help="BytePlus Ark's Asia Pacific endpoint. This provider is separate from Volcengine Ark Agent Plan.",
),
_responses_compat(
"ark-agent-plan-cn",
"Volcengine Ark Agent Plan",
base_url="https://ark.cn-beijing.volces.com/api/plan/v3",
recommended_model="doubao-seed-evolving",
env_key="ARK_AGENT_PLAN_CN_API_KEY",
endpoint_help="Volcengine Ark Agent Plan's China (Beijing) endpoint. It requires an Agent Plan API key.",
),
# OpenAI-compatible vendors, listed as first-class providers so users don't need to know the
# "point the OpenAI slot at a different endpoint" trick (owner call, 2026-07-04). Each keeps
# its own key profile; the endpoint is prefilled and editable (regional variants in `help`).
+68
View File
@@ -338,6 +338,74 @@ def test_compat_builder_never_leaks_the_openai_key(monkeypatch):
build_provider_client("kimi", {}, None)
ARK_RESPONSES_VENDORS = {
"ark": {
"base_url": "https://ark.ap-southeast.bytepluses.com/api/v3",
"env_key": "ARK_API_KEY",
"recommended_model": "dola-seed-evolving-latest-version",
},
"ark-agent-plan-cn": {
"base_url": "https://ark.cn-beijing.volces.com/api/plan/v3",
"env_key": "ARK_AGENT_PLAN_CN_API_KEY",
"recommended_model": "doubao-seed-evolving",
},
}
def test_ark_responses_descriptors_are_separate():
from coworker.providers.registry import get_descriptor
for name, expected in ARK_RESPONSES_VENDORS.items():
d = get_descriptor(name)
assert d is not None and d.needs_key, name
assert d.env_key == expected["env_key"]
assert d.recommended_model == expected["recommended_model"]
assert "Responses API" in d.blurb
base = next(f for f in d.fields if f.key == "base_url")
assert base.default == expected["base_url"]
assert not base.required
def test_ark_responses_builders_use_their_own_keys_and_endpoints(monkeypatch):
from coworker.providers.openai_responses import OpenAIResponsesProvider
from coworker.providers.registry import build_provider_client
monkeypatch.setenv("ARK_AGENT_PLAN_CN_API_KEY", "plan-key")
bp = build_provider_client("ark", {"api_key": "bp-key"}, None)
plan = build_provider_client("ark-agent-plan-cn", {}, None)
assert isinstance(bp, OpenAIResponsesProvider)
assert (bp._api_key, bp._base_url) == (
"bp-key",
ARK_RESPONSES_VENDORS["ark"]["base_url"],
)
assert isinstance(plan, OpenAIResponsesProvider)
assert (plan._api_key, plan._base_url) == (
"plan-key",
ARK_RESPONSES_VENDORS["ark-agent-plan-cn"]["base_url"],
)
def test_ark_responses_never_leak_the_openai_key(monkeypatch):
import pytest
from coworker.providers.registry import build_provider_client
monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-real")
monkeypatch.delenv("ARK_API_KEY", raising=False)
with pytest.raises(RuntimeError, match="BytePlus Ark"):
build_provider_client("ark", {}, None)
def test_existing_chat_compat_paths_unchanged():
"""Lockdown: adding Responses vendors must not migrate existing compat providers."""
from coworker.providers.registry import build_provider_client
provider = build_provider_client("deepseek", {"api_key": "ds-key"}, None)
assert isinstance(provider, OpenAIProvider)
assert provider._base_url == COMPAT_VENDORS["deepseek"]
def test_compat_models_route_and_get_tool_capabilities():
from coworker.providers.router import ProviderRouter