Files
openworker/tests/test_providers.py
Rohit C Prasad 6fbd0f995f Add Ox Alpha (stealth preview) via OpenRouter
Catalog-checked: 1,048,576 ctx, tool calling; matrix cap 60->65.
2026-08-25 09:00:15 -07:00

620 lines
23 KiB
Python

"""P0 gate tests — provider layer. SDK-free (inject a fake OpenAI client)."""
from __future__ import annotations
import json
from types import SimpleNamespace
from coworker.providers import (
AssistantTurn,
ModelCapabilities,
OpenAIProvider,
ToolCall,
capabilities_for,
)
class _FakeCompletions:
def __init__(self, response):
self._response = response
self.calls: list[dict] = []
def create(self, **kwargs):
self.calls.append(kwargs)
return self._response
class _FakeClient:
def __init__(self, response):
self.chat = SimpleNamespace(completions=_FakeCompletions(response))
def _response(content=None, tool_calls=None, finish_reason="stop"):
message = SimpleNamespace(content=content, tool_calls=tool_calls)
choice = SimpleNamespace(message=message, finish_reason=finish_reason)
return SimpleNamespace(choices=[choice])
def test_complete_returns_text():
client = _FakeClient(_response(content="hello there"))
provider = OpenAIProvider(client=client)
turn = provider.complete(
model="gpt-5.5", messages=[{"role": "user", "content": "hi"}]
)
assert isinstance(turn, AssistantTurn)
assert turn.text == "hello there"
assert turn.tool_calls == []
assert turn.has_tool_calls is False
assert turn.finish_reason == "stop"
def test_complete_parses_tool_calls():
tc = SimpleNamespace(
id="call_1",
function=SimpleNamespace(
name="read_file", arguments=json.dumps({"path": "a.py"})
),
)
client = _FakeClient(_response(tool_calls=[tc], finish_reason="tool_calls"))
provider = OpenAIProvider(client=client)
turn = provider.complete(
model="gpt-5.5",
messages=[],
tools=[{"type": "function", "function": {"name": "read_file"}}],
)
assert turn.has_tool_calls
assert turn.tool_calls[0] == ToolCall(
id="call_1", name="read_file", arguments={"path": "a.py"}
)
# tools forwarded to the API
assert "tools" in client.chat.completions.calls[0]
def test_complete_tolerates_bad_tool_args():
tc = SimpleNamespace(
id="call_2", function=SimpleNamespace(name="x", arguments="{not json")
)
client = _FakeClient(_response(tool_calls=[tc]))
provider = OpenAIProvider(client=client)
turn = provider.complete(model="gpt-5.5", messages=[])
assert turn.tool_calls[0].arguments == {"_raw": "{not json"}
def test_tools_omitted_when_none():
client = _FakeClient(_response(content="x"))
provider = OpenAIProvider(client=client)
provider.complete(model="gpt-5.5", messages=[])
assert "tools" not in client.chat.completions.calls[0]
def test_settings_forwarded():
client = _FakeClient(_response(content="x"))
provider = OpenAIProvider(client=client)
provider.complete(model="gpt-5.5", messages=[], temperature=0.2)
assert client.chat.completions.calls[0]["temperature"] == 0.2
def test_capabilities_known_models():
assert capabilities_for("gpt-5.5").tools is True
assert capabilities_for("openai:gpt-5.5").vision is True # provider prefix stripped
assert capabilities_for("o3-mini").parallel_tool_calls is False
assert capabilities_for("deepseek-chat").tools is True
def test_capabilities_via_provider():
provider = OpenAIProvider(client=_FakeClient(_response()))
caps = provider.capabilities("gpt-5.5")
assert isinstance(caps, ModelCapabilities)
assert caps.tools is True
# -- GPT-5.6 tools + reasoning_effort on chat/completions (owner repro 2026-07-14) ----
# The API defaults these models to effort "medium" and then rejects function tools:
# "Function tools with reasoning_effort are not supported for gpt-5.6-sol in
# /v1/chat/completions. To use function tools, use /v1/responses or set
# reasoning_effort to 'none'." Until we speak the Responses API, we pin effort none.
_TOOLS = [{"type": "function", "function": {"name": "read_file"}}]
_EFFORT_400 = (
"Error code: 400 - {'error': {'message': \"Function tools with reasoning_effort "
"are not supported for %s in /v1/chat/completions. To use function tools, use "
"/v1/responses or set reasoning_effort to 'none'.\", 'type': "
"'invalid_request_error', 'param': 'reasoning_effort', 'code': None}}"
)
def test_gpt56_tools_pin_reasoning_effort_none():
client = _FakeClient(_response(content="x"))
provider = OpenAIProvider(client=client)
calls = client.chat.completions.calls
for model in ("gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"):
provider.complete(model=model, messages=[], tools=_TOOLS)
assert [c["reasoning_effort"] for c in calls] == ["none"] * 3
# an explicit caller choice is respected on the first attempt
provider.complete(
model="gpt-5.6-sol", messages=[], tools=_TOOLS, reasoning_effort="low"
)
assert calls[3]["reasoning_effort"] == "low"
# no tools, or another model → the request is untouched
provider.complete(model="gpt-5.6-sol", messages=[])
provider.complete(model="gpt-5.5", messages=[], tools=_TOOLS)
assert "reasoning_effort" not in calls[4] and "reasoning_effort" not in calls[5]
class _EffortRejectingCompletions:
"""Behaves like the live API: tools + any effort other than 'none' → the 400."""
def __init__(self, response):
self._response = response
self.calls: list[dict] = []
def create(self, **kwargs):
self.calls.append(kwargs)
if kwargs.get("tools") and kwargs.get("reasoning_effort") != "none":
raise RuntimeError(_EFFORT_400 % kwargs["model"])
if kwargs.get("stream"):
return iter([_chunk(content="ok"), _chunk(finish="stop")])
return self._response
def test_effort_400_from_an_unpinned_model_retries_once_at_none():
# a hypothetical next generation we haven't listed yet — proactive pin misses it
client = _FakeClient(_response(content="x"))
client.chat.completions = _EffortRejectingCompletions(_response(content="x"))
provider = OpenAIProvider(client=client)
turn = provider.complete(model="gpt-5.7-sol", messages=[], tools=_TOOLS)
calls = client.chat.completions.calls
assert turn.text == "x" and len(calls) == 2
assert "reasoning_effort" not in calls[0] and calls[1]["reasoning_effort"] == "none"
# streaming path retries the same way
out = list(provider.stream(model="gpt-5.7-sol", messages=[], tools=_TOOLS))
assert out[-1].turn.text == "ok" and len(client.chat.completions.calls) == 4
def test_max_tokens_rejection_retries_as_max_completion_tokens():
"""Reasoning-routed models 400 on max_tokens (want max_completion_tokens); compat
servers know only max_tokens — so the swap happens on rejection, never up front.
(Owner-hit 2026-07-20: the auto-title call silently no-oped on gpt-5.6-sol.)"""
class _MaxTokensRejecting:
def __init__(self, response):
self._response = response
self.calls: list[dict] = []
def create(self, **kwargs):
self.calls.append(kwargs)
if "max_tokens" in kwargs:
raise RuntimeError(
"Error code: 400 - Unsupported parameter: 'max_tokens' is not "
"supported with this model. Use 'max_completion_tokens' instead."
)
return self._response
client = _FakeClient(_response(content="Jira vs Linear"))
client.chat.completions = _MaxTokensRejecting(_response(content="Jira vs Linear"))
provider = OpenAIProvider(client=client)
turn = provider.complete(model="gpt-5.6-sol", messages=[], max_tokens=64)
calls = client.chat.completions.calls
assert turn.text == "Jira vs Linear" and len(calls) == 2
assert calls[0]["max_tokens"] == 64
assert "max_tokens" not in calls[1] and calls[1]["max_completion_tokens"] == 64
def test_unrelated_400s_are_not_retried():
class _AlwaysRejects:
calls: list = []
def create(self, **kwargs):
self.calls.append(kwargs)
raise RuntimeError("Error code: 400 - context_length_exceeded")
client = _FakeClient(_response(content="x"))
client.chat.completions = _AlwaysRejects()
provider = OpenAIProvider(client=client)
try:
provider.complete(model="gpt-5.5", messages=[], tools=_TOOLS)
raise AssertionError("should have raised")
except RuntimeError:
pass
assert len(client.chat.completions.calls) == 1 # no blind second attempt
# -- streaming ------------------------------------------------------------------
def _chunk(content=None, tool_call=None, finish=None):
delta = SimpleNamespace(
content=content, tool_calls=[tool_call] if tool_call else None
)
return SimpleNamespace(choices=[SimpleNamespace(delta=delta, finish_reason=finish)])
class _StreamClient:
def __init__(self, chunks):
self.chat = SimpleNamespace(
completions=SimpleNamespace(create=lambda **kwargs: iter(chunks))
)
def test_stream_text_deltas():
chunks = [_chunk(content="Hel"), _chunk(content="lo"), _chunk(finish="stop")]
provider = OpenAIProvider(client=_StreamClient(chunks))
out = list(provider.stream(model="gpt-5.5", messages=[]))
assert [c.text_delta for c in out if c.text_delta] == ["Hel", "lo"]
assert out[-1].turn.text == "Hello"
assert out[-1].turn.finish_reason == "stop"
def test_stream_accumulates_tool_calls():
tc1 = SimpleNamespace(
index=0,
id="call_1",
function=SimpleNamespace(name="read_file", arguments='{"pa'),
)
tc2 = SimpleNamespace(
index=0, id=None, function=SimpleNamespace(name=None, arguments='th": "a.py"}')
)
chunks = [_chunk(tool_call=tc1), _chunk(tool_call=tc2), _chunk(finish="tool_calls")]
provider = OpenAIProvider(client=_StreamClient(chunks))
turn = list(provider.stream(model="gpt-5.5", messages=[]))[-1].turn
assert turn.tool_calls[0] == ToolCall(
id="call_1", name="read_file", arguments={"path": "a.py"}
)
# -- OpenAI-compatible vendor providers (Z AI, DeepSeek, Kimi, MiniMax, Qwen, xAI, Mistral) ------
COMPAT_VENDORS = {
"zai": "https://api.z.ai/api/paas/v4",
"deepseek": "https://api.deepseek.com",
"kimi": "https://api.moonshot.ai/v1",
"minimax": "https://api.minimax.io/v1",
"qwen": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
"xai": "https://api.x.ai/v1",
"mistral": "https://api.mistral.ai/v1",
}
def test_compat_vendor_descriptors_ship_prefilled_endpoints():
from coworker.providers.registry import get_descriptor
for name, endpoint in COMPAT_VENDORS.items():
d = get_descriptor(name)
assert d is not None and d.needs_key, name
base = next(f for f in d.fields if f.key == "base_url")
assert base.default == endpoint # prefilled, editable
assert not base.required # blank falls back to the default in the builder
assert "OpenAI-compatible" in d.blurb
assert d.env_key and d.recommended_model
def test_compat_builder_defaults_and_profile_override(monkeypatch):
from coworker.providers.registry import build_provider_client
p = build_provider_client("zai", {"api_key": "zk"}, None)
assert p._base_url == COMPAT_VENDORS["zai"]
assert p._api_key == "zk"
override = "https://open.bigmodel.cn/api/paas/v4"
p2 = build_provider_client("zai", {"api_key": "zk", "base_url": override}, None)
assert p2._base_url == override
def test_compat_builder_env_key_fallback(monkeypatch):
from coworker.providers.registry import build_provider_client
monkeypatch.setenv("DEEPSEEK_API_KEY", "ds-key")
p = build_provider_client("deepseek", {}, None)
assert p._api_key == "ds-key"
assert p._base_url == COMPAT_VENDORS["deepseek"]
def test_compat_builder_never_leaks_the_openai_key(monkeypatch):
"""A configured OPENAI_API_KEY must never be sent to a different vendor's endpoint —
a missing vendor key fails fast with a vendor-named error instead."""
import pytest
from coworker.providers.registry import build_provider_client
monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-real")
monkeypatch.delenv("MOONSHOT_API_KEY", raising=False)
with pytest.raises(RuntimeError, match="Kimi"):
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",
"reasoning_summary": False,
},
"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",
"reasoning_summary": True,
},
}
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_builder_capabilities_PathsUnchanged(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"],
)
assert bp._reasoning_summary is ARK_RESPONSES_VENDORS["ark"]["reasoning_summary"]
assert plan._reasoning_summary is ARK_RESPONSES_VENDORS["ark-agent-plan-cn"][
"reasoning_summary"
]
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_ark_curated_models_are_strict_allowlists():
from coworker.providers.matrix import models_for_provider
assert models_for_provider("ark") == [
"dola-seed-evolving-latest-version",
"dola-seed-2-1-turbo-260628",
]
assert models_for_provider("ark-agent-plan-cn") == [
"doubao-seed-evolving",
"doubao-seed-2.1-turbo",
]
def test_ark_models_route_and_get_verified_agent_capabilities():
from coworker.providers.router import ProviderRouter
models = (
"ark:dola-seed-evolving-latest-version",
"ark:dola-seed-2-1-turbo-260628",
"ark-agent-plan-cn:doubao-seed-evolving",
"ark-agent-plan-cn:doubao-seed-2.1-turbo",
)
router = ProviderRouter.__new__(ProviderRouter)
for model in models:
prefix, bare = model.split(":", 1)
assert router._provider_name(model) == prefix
assert ProviderRouter._bare(model) == bare
caps = capabilities_for(model)
assert caps.tools and caps.parallel_tool_calls and caps.streaming
assert not caps.vision
def test_ark_recommended_models_are_curated():
from coworker.providers.matrix import models_for_provider
from coworker.providers.registry import get_descriptor
for name in ARK_RESPONSES_VENDORS:
d = get_descriptor(name)
assert d.recommended_model in models_for_provider(name)
def test_compat_models_route_and_get_tool_capabilities():
from coworker.providers.router import ProviderRouter
router = ProviderRouter.__new__(
ProviderRouter
) # only using _provider_name (stateless)
for model in (
"zai:glm-5.2",
"deepseek:deepseek-v4-flash",
"kimi:kimi-k2.6",
"minimax:MiniMax-M2.5",
"qwen:qwen3-max",
"xai:grok-4.3",
"mistral:mistral-large-latest",
):
prefix = model.split(":", 1)[0]
assert router._provider_name(model) == prefix
assert ProviderRouter._bare(model) == model.split(":", 1)[1]
caps = capabilities_for(model)
assert caps.tools and caps.streaming
def test_compat_recommended_models_are_in_the_suggested_lists():
"""set_provider only auto-adds the recommended model if it's in _suggested_models —
keep the registry and the manager's COMPAT_MODELS table in lockstep."""
from coworker.providers.registry import get_descriptor
from coworker.server.manager import SessionManager
for name in COMPAT_VENDORS:
d = get_descriptor(name)
assert d.recommended_model in SessionManager.COMPAT_MODELS[name], name
# -- curated model matrix (labels + capabilities by full routed id) -----------------
def test_matrix_answers_capabilities_for_reseller_ids():
"""Reseller ids ('together:zai-org/GLM-5.2') defeat the name-prefix heuristics — the
matrix must answer them exactly, with tool calling on."""
for mid in (
"together:zai-org/GLM-5.2",
"together:meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
"fireworks:accounts/fireworks/models/kimi-k2p6",
"openrouter:z-ai/glm-5.2",
"openrouter:meta-llama/llama-4-maverick",
):
caps = capabilities_for(mid)
assert caps.tools and caps.parallel_tool_calls and caps.streaming
def test_matrix_labels_and_custom_model_fallback():
from coworker.providers.matrix import MATRIX, model_labels
labels = model_labels()
assert labels["together:zai-org/GLM-5.2"] == "GLM-5.2 · via Together"
assert labels["zai:glm-5.2"] == "GLM-5.2 · Z AI"
# Deliberately small: agent-capable current models only (owner call, 2026-07-04).
# 60→65 (2026-08-24): the stealth ox-alpha preview slug tipped it; reclaim slack by
# pruning retired entries before raising this again.
assert len(MATRIX) < 65
assert all(e.caps.tools for e in MATRIX.values())
# A custom (unlisted) reseller model falls back to the conservative default — usable,
# but at the user's own risk (no parallel tool calls assumed).
caps = capabilities_for("together:some-org/Brand-New-Model")
assert caps.tools and not caps.parallel_tool_calls
def test_reseller_descriptors_and_matrix_stay_in_lockstep():
"""Reseller suggested models derive from the matrix, and each descriptor's
recommended model must be one of them (set_provider's auto-add depends on it)."""
from coworker.providers.matrix import models_for_provider
from coworker.providers.registry import get_descriptor
for name in ("together", "fireworks", "openrouter"):
d = get_descriptor(name)
assert d is not None and d.needs_key
curated = models_for_provider(name)
assert curated and d.recommended_model in curated
# full ids in the matrix must round-trip: prefix + bare == matrix key
base = next(f for f in d.fields if f.key == "base_url")
assert base.default.startswith("https://")
def test_foreign_sidecars_stripped_from_outbound_messages():
"""Provider-private sidecars (`_gemini` thought signatures et al) must never reach the
OpenAI wire — it and its compat servers reject unknown message fields."""
client = _FakeClient(_response(content="ok"))
provider = OpenAIProvider(client=client)
provider.complete(
model="gpt-5.5",
messages=[
{"role": "user", "content": "hi"},
{"role": "assistant", "content": "prev", "_gemini": {"call_sigs": ["x"]}},
],
)
sent = client.chat.completions.calls[0]["messages"]
assert sent[1] == {"role": "assistant", "content": "prev"}
def test_stream_reasoning_content_deltas():
"""DeepSeek-style thinking: reasoning_content deltas surface as reasoning chunks and
land on the final turn — never mixed into the answer text."""
def rchunk(text):
delta = SimpleNamespace(content=None, tool_calls=None, reasoning_content=text)
return SimpleNamespace(choices=[SimpleNamespace(delta=delta, finish_reason=None)])
chunks = [rchunk("hmm "), rchunk("okay."), _chunk(content="Answer"), _chunk(finish="stop")]
provider = OpenAIProvider(client=_StreamClient(chunks))
out = list(provider.stream(model="deepseek-v4-pro", messages=[]))
assert [c.reasoning_delta for c in out if c.reasoning_delta] == ["hmm ", "okay."]
final = out[-1].turn
assert final.text == "Answer" and final.reasoning == "hmm okay."
def test_complete_picks_up_reasoning_content():
message = SimpleNamespace(content="Answer", tool_calls=None, reasoning_content="deep thought")
choice = SimpleNamespace(message=message, finish_reason="stop")
provider = OpenAIProvider(client=_FakeClient(SimpleNamespace(choices=[choice])))
turn = provider.complete(model="deepseek-v4-pro", messages=[{"role": "user", "content": "x"}])
assert turn.text == "Answer" and turn.reasoning == "deep thought"
def test_default_max_tokens_injected_and_caller_setting_wins():
"""Compat servers left to their OWN defaults cap completions absurdly low
(owner-hit 2026-08-15: Together defaulted Kimi K3 to ~2k tokens, so every report
write truncated mid-arguments). The request always names a ceiling now."""
from coworker.providers.openai_provider import DEFAULT_MAX_TOKENS
client = _FakeClient(_response(content="ok"))
provider = OpenAIProvider(client=client)
provider.complete(model="kimi-k3", messages=[])
assert client.chat.completions.calls[0]["max_tokens"] == DEFAULT_MAX_TOKENS
client2 = _FakeClient(_response(content="ok"))
provider2 = OpenAIProvider(client=client2)
provider2.complete(model="kimi-k3", messages=[], max_tokens=512)
assert client2.chat.completions.calls[0]["max_tokens"] == 512
def test_over_limit_max_tokens_is_dropped_and_retried():
"""A model whose completion limit sits below our default must not surface the 400:
drop the param, retry on the server's own default (yesterday's behavior, at worst)."""
class _LimitRejecting:
def __init__(self, response):
self._response = response
self.calls: list[dict] = []
def create(self, **kwargs):
self.calls.append(kwargs)
if "max_tokens" in kwargs:
raise RuntimeError(
"Error code: 400 - max_tokens must be at most 8193 for this model"
)
return self._response
client = _FakeClient(_response(content="ok"))
client.chat.completions = _LimitRejecting(_response(content="ok"))
provider = OpenAIProvider(client=client)
turn = provider.complete(model="tiny-model", messages=[])
calls = client.chat.completions.calls
assert turn.text == "ok" and len(calls) == 2
assert "max_tokens" not in calls[1]