mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-03 04:49:26 +00:00
feat: register BytePlus and Volcengine Ark providers
This commit is contained in:
@@ -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`).
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user