Merge branch 'main' of https://github.com/andrewyng/openworker into feature/permission-modes

# Conflicts:
#	tests/test_openai_responses.py
This commit is contained in:
Devika Verma
2026-08-20 09:01:15 +05:30
14 changed files with 463 additions and 19 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ Under the hood:
Model access is yours: pick a provider, paste your key, switch anytime. Supported out of the box:
**OpenAI · Anthropic · Google Gemini · Inkling (Thinking Machines) · GLM (Z.ai) · DeepSeek · Kimi (Moonshot) · Qwen · MiniMax · Mistral · Grok (xAI)** - plus open-weight models via **Together** and **Fireworks**, and fully local models via **Ollama**.
**OpenAI · Anthropic · Google Gemini · BytePlus Ark · Volcengine Ark Agent Plan · Inkling (Thinking Machines) · GLM (Z.ai) · DeepSeek · Kimi (Moonshot) · Qwen · MiniMax · Mistral · Grok (xAI)** - plus open-weight models via **Together** and **Fireworks**, and fully local models via **Ollama**.
A curated model list marks what we've verified for tool-calling work. Adding any model string works at your own risk.
+15
View File
@@ -85,6 +85,21 @@ MATRIX: dict[str, ModelEntry] = {
"gemini:gemini-2.5-flash": ModelEntry(
"Gemini 2.5 Flash · Google", _AGENTIC_VISION, 1_048_576
),
# Ark Responses API providers (verified 2026-08-14). BytePlus pay-as-you-go and
# Volcengine Agent Plan intentionally use separate provider prefixes because their
# endpoints, credentials, regions, and model catalogs are not interchangeable.
"ark:dola-seed-evolving-latest-version": ModelEntry(
"Dola Seed Evolving · BytePlus Ark", context_window=256_000
),
"ark:dola-seed-2-1-turbo-260628": ModelEntry(
"Dola Seed 2.1 Turbo · BytePlus Ark", context_window=256_000
),
"ark-agent-plan-cn:doubao-seed-evolving": ModelEntry(
"Doubao Seed Evolving · Volcengine Agent Plan", context_window=256_000
),
"ark-agent-plan-cn:doubao-seed-2.1-turbo": ModelEntry(
"Doubao Seed 2.1 Turbo · Volcengine Agent Plan", context_window=256_000
),
# -- direct OpenAI-compatible vendors ----------------------------------------
# Muse Spark (Meta Model API, public preview 2026-07-09): multimodal + tools via
# their OpenAI-compat surface. Vision yes; PDFs unverified over compat — falls
+19 -8
View File
@@ -1,4 +1,4 @@
"""OpenAI Responses provider — native OpenAI models via `/v1/responses`.
"""OpenAI Responses provider — native and compatible models via `/responses`.
Chat Completions rejects function tools combined with any `reasoning_effort` other than
`none` on GPT-5.6+ ("use /v1/responses"), which had reasoning pinned OFF for native OpenAI
@@ -8,9 +8,10 @@ reasoning + tools at real effort levels, streamed reasoning summaries (→ the s
chain-of-thought continuity across tool round-trips via `store: false` +
`include: ["reasoning.encrypted_content"]` — nothing retained server-side.
Routing: the `openai` provider entry with NO custom base_url builds this class; a custom
endpoint (Azure, vLLM, any OpenAI-compatible gateway) and every compat vendor keep the
Chat Completions `OpenAIProvider` (registry.py).
Routing: the `openai` provider entry with NO custom base_url builds this class. Most custom
endpoints (Azure, vLLM, and the existing compat vendors) keep the Chat Completions
`OpenAIProvider`; vendors that explicitly implement the Responses wire can opt into this
class with their own base URL (registry.py).
Like the other native providers, this is mostly a pair of pure converters from the
canonical OpenAI-chat-shaped history to Responses `input` items. What the converters
@@ -309,14 +310,20 @@ class OpenAIResponsesProvider(ProviderClient):
default_model: str = "gpt-5.6-sol",
api_key: Optional[str] = None,
secrets: Any = None,
base_url: Optional[str] = None,
reasoning_summary: bool = True,
):
# Same deferred-client contract as OpenAIProvider: built lazily so an engine can be
# assembled before any key exists; key resolves at call time (explicit → env →
# SecretStore). Tests inject a `client` directly. No base_url — a custom endpoint
# routes to the Chat Completions provider instead (registry.py).
# SecretStore). Tests inject a `client` directly. `base_url` is opt-in: stock OpenAI
# leaves it unset, while Responses-compatible vendors can supply their own endpoint.
self._client = client
self._api_key = api_key
self._secrets = secrets
self._base_url = (base_url or "").strip().rstrip("/") or None
if not isinstance(reasoning_summary, bool):
raise TypeError("reasoning_summary must be a bool")
self._reasoning_summary = reasoning_summary
self.default_model = default_model
def _ensure_client(self) -> Any:
@@ -330,7 +337,10 @@ class OpenAIResponsesProvider(ProviderClient):
"No model API key configured. Set OPENAI_API_KEY in the environment, "
"or add your key in Manage → Settings."
)
self._client = OpenAI(api_key=key)
kwargs = {"api_key": key}
if self._base_url:
kwargs["base_url"] = self._base_url
self._client = OpenAI(**kwargs)
return self._client
def _request_kwargs(
@@ -351,9 +361,10 @@ class OpenAIResponsesProvider(ProviderClient):
# `_openai` sidecar instead, and summaries feed the GUI's thinking display.
"store": False,
"include": ["reasoning.encrypted_content"],
"reasoning": {"summary": "auto"},
**{k: v for k, v in settings.items() if k in _SETTINGS_WHITELIST},
}
if self._reasoning_summary:
kwargs["reasoning"] = {"summary": "auto"}
if instructions:
kwargs["instructions"] = instructions
if tools:
+115 -3
View File
@@ -210,6 +210,37 @@ 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,
*,
reasoning_summary: bool = True,
):
"""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,
reasoning_summary=reasoning_summary,
)
return build
def _compat(
name: str,
title: str,
@@ -248,6 +279,49 @@ def _compat(
)
def _responses_compat(
name: str,
title: str,
*,
base_url: str,
recommended_model: str,
env_key: str,
endpoint_help: str = "",
reasoning_summary: bool = True,
) -> 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,
reasoning_summary=reasoning_summary,
),
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 +536,26 @@ 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.",
reasoning_summary=False,
),
_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`).
@@ -814,9 +908,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
@@ -843,6 +939,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), ""
+8
View File
@@ -45,6 +45,10 @@ const SETTINGS = {
model_labels: {
"anthropic:claude-opus-4-8": "Claude Opus 4.8 · Anthropic",
"zai:glm-5.2": "GLM-5.2 · Z AI",
"ark:dola-seed-evolving-latest-version": "Dola Seed Evolving · BytePlus Ark",
"ark:dola-seed-2-1-turbo-260628": "Dola Seed 2.1 Turbo · BytePlus Ark",
"ark-agent-plan-cn:doubao-seed-evolving": "Doubao Seed Evolving · Volcengine Agent Plan",
"ark-agent-plan-cn:doubao-seed-2.1-turbo": "Doubao Seed 2.1 Turbo · Volcengine Agent Plan",
},
// Context windows (subset — mirrors /v1/settings.model_context_windows); drives the
// composer usage chip's context-fill meter.
@@ -336,6 +340,10 @@ const PROVIDERS = [
{ name: "anthropic", title: "Claude (Anthropic)", needs_key: true, fields: [{ key: "api_key", label: "API key", secret: true, required: true, help: "", placeholder: "sk-…" }], configured: true, values: {}, suggested_models: ["claude-opus-4-8"], key_set_at: null, last_used_at: null },
// zai: an OpenAI-compatible vendor — unconfigured, with a prefilled editable endpoint + blurb.
{ name: "zai", title: "Z AI (GLM)", needs_key: true, blurb: "Uses Z AI's OpenAI-compatible API — the endpoint is prefilled, just add your key.", fields: [{ key: "api_key", label: "Z AI API key", secret: true, required: true, help: "", placeholder: "" }, { key: "base_url", label: "Endpoint", secret: false, required: false, help: "Prefilled with Z AI's international endpoint.", placeholder: "https://api.z.ai/api/paas/v4", default: "https://api.z.ai/api/paas/v4" }], configured: false, values: {}, suggested_models: ["glm-5.2"], key_set_at: null, last_used_at: null },
// Ark uses two provider identities: BytePlus pay-as-you-go and Volcengine Agent Plan CN
// have independent credentials, endpoints, and strict curated model lists.
{ name: "ark", title: "BytePlus Ark", needs_key: true, blurb: "Uses BytePlus Ark's OpenAI-compatible Responses API — the endpoint is prefilled, just add your key.", fields: [{ key: "api_key", label: "BytePlus Ark API key", secret: true, required: true, help: "", placeholder: "" }, { key: "base_url", label: "Endpoint", secret: false, required: false, help: "BytePlus Ark's Asia Pacific endpoint.", placeholder: "https://ark.ap-southeast.bytepluses.com/api/v3", default: "https://ark.ap-southeast.bytepluses.com/api/v3" }], configured: false, values: {}, suggested_models: ["dola-seed-evolving-latest-version", "dola-seed-2-1-turbo-260628"], key_set_at: null, last_used_at: null },
{ name: "ark-agent-plan-cn", title: "Volcengine Ark Agent Plan", needs_key: true, blurb: "Uses Volcengine Ark Agent Plan's OpenAI-compatible Responses API — the endpoint is prefilled, just add your key.", fields: [{ key: "api_key", label: "Volcengine Ark Agent Plan API key", secret: true, required: true, help: "", placeholder: "" }, { key: "base_url", label: "Endpoint", secret: false, required: false, help: "Volcengine Ark Agent Plan's China (Beijing) endpoint.", placeholder: "https://ark.cn-beijing.volces.com/api/plan/v3", default: "https://ark.cn-beijing.volces.com/api/plan/v3" }], configured: false, values: {}, suggested_models: ["doubao-seed-evolving", "doubao-seed-2.1-turbo"], key_set_at: null, last_used_at: null },
// ollama: keyless local provider — "configured" without proving anything runs; the
// onboarding gallery shows "No key needed" and its form is endpoint + Detect (§39).
{ name: "ollama", title: "Ollama (local models)", needs_key: false, fields: [{ key: "base_url", label: "Endpoint", secret: false, required: false, help: "", placeholder: "http://127.0.0.1:11434", default: "http://127.0.0.1:11434" }], configured: true, values: {}, suggested_models: ["qwen3-coder:30b"], key_set_at: null, last_used_at: null },
+38
View File
@@ -71,6 +71,44 @@ test("Models: provider gallery states; vendor form previews models", async ({ pa
await expect(page.getByTestId("set-provider-openai")).toBeVisible();
});
test("Models: BytePlus and Volcengine Ark stay visually and operationally separate", async ({ page }) => {
await page.goto("/");
await page.getByTestId("account-row").click();
await page.getByRole("button", { name: "Settings", exact: true }).click();
await page.getByRole("button", { name: "Models", exact: true }).click();
const byteplusCard = page.getByTestId("set-provider-ark");
const volcengineCard = page.getByTestId("set-provider-ark-agent-plan-cn");
await expect(byteplusCard).toContainText("BytePlus Ark");
await expect(volcengineCard).toContainText("Volcengine Ark Agent Plan");
const byteplusLogo = await byteplusCard.locator("img").getAttribute("src");
const volcengineLogo = await volcengineCard.locator("img").getAttribute("src");
expect(byteplusLogo).toBeTruthy();
expect(volcengineLogo).toBeTruthy();
expect(byteplusLogo).not.toBe(volcengineLogo);
await byteplusCard.click();
await page.getByTestId("set-endpoint-link").click();
await expect(page.getByTestId("set-field-base_url")).toHaveValue(
"https://ark.ap-southeast.bytepluses.com/api/v3",
);
let preview = page.getByTestId("model-preview");
await expect(preview).toContainText("Dola Seed Evolving · BytePlus Ark");
await expect(preview).toContainText("Dola Seed 2.1 Turbo · BytePlus Ark");
await expect(preview).not.toContainText("Doubao Seed");
await page.getByTestId("set-back").click();
await volcengineCard.click();
await page.getByTestId("set-endpoint-link").click();
await expect(page.getByTestId("set-field-base_url")).toHaveValue(
"https://ark.cn-beijing.volces.com/api/plan/v3",
);
preview = page.getByTestId("model-preview");
await expect(preview).toContainText("Doubao Seed Evolving · Volcengine Agent Plan");
await expect(preview).toContainText("Doubao Seed 2.1 Turbo · Volcengine Agent Plan");
await expect(preview).not.toContainText("Dola Seed");
});
// UX-021: a configured provider's form shows the in-field saved state and the Remove key…
// affordance; removing reverts the card to "Not set up".
test("Models: Remove key reverts a configured provider", async ({ page }) => {
@@ -2,7 +2,7 @@
// only the selected method's fields render, and clicking a segment switches them.
import { afterEach, describe, expect, it, vi } from "vitest";
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { ProviderForm, type ProviderSetupState } from "./ProviderSetup";
import { KEY_HELP, ProviderForm, ProviderMark, type ProviderSetupState } from "./ProviderSetup";
import type { ProviderInfo } from "../api";
vi.mock("../tauri", () => ({ openExternal: vi.fn() }));
@@ -94,3 +94,29 @@ describe("ProviderForm auth-method choice", () => {
expect(screen.queryByTestId("t-field-bedrock_api_key")).toBeNull();
});
});
describe("Ark provider presentation", () => {
it("uses separate BytePlus and Volcengine brand marks", () => {
const { container, rerender } = render(
<ProviderMark name="ark" title="BytePlus Ark" />,
);
expect(container.querySelector("img")).toBeTruthy();
rerender(
<ProviderMark
name="ark-agent-plan-cn"
title="Volcengine Ark Agent Plan"
/>,
);
expect(container.querySelector("img")).toBeTruthy();
});
it("links each provider to its own API key console", () => {
expect(KEY_HELP.ark.url).toBe(
"https://console.byteplus.com/ark/region:ark+ap-southeast-1/apiKey",
);
expect(KEY_HELP["ark-agent-plan-cn"].url).toContain(
"advancedActiveKey=agentPlan",
);
});
});
@@ -21,6 +21,8 @@ export const KEY_HELP: Record<string, { url: string; label: string }> = {
anthropic: { url: "https://console.anthropic.com/settings/keys", label: "console.anthropic.com" },
openai: { url: "https://platform.openai.com/api-keys", label: "platform.openai.com" },
gemini: { url: "https://aistudio.google.com/apikey", label: "aistudio.google.com" },
ark: { url: "https://console.byteplus.com/ark/region:ark+ap-southeast-1/apiKey", label: "console.byteplus.com" },
"ark-agent-plan-cn": { url: "https://console.volcengine.com/ark/region:cn-beijing/openManagement?LLM=%7B%7D&advancedActiveKey=agentPlan", label: "console.volcengine.com" },
openrouter: { url: "https://openrouter.ai/keys", label: "openrouter.ai" },
bedrock: { url: "https://console.aws.amazon.com/bedrock/home#/api-keys", label: "the AWS Bedrock console" },
fireworks: { url: "https://fireworks.ai/account/api-keys", label: "fireworks.ai" },
+11 -5
View File
@@ -1,13 +1,15 @@
// Provider logo registry (UX-DECISIONS §39): official brand marks for the onboarding
// provider gallery, vendored from the MIT-licensed lobe-icons set (same
// bundled-asset posture as the connector registry — no CDN at runtime). Keys are
// /v1/providers names; unknown names get no mark (the gallery falls back to a
// neutral monogram). PROVIDER_ORDER is the gallery order — recognition first,
// long tail behind the scroll fold.
// provider gallery. Most are vendored from the MIT-licensed lobe-icons set; BytePlus is
// its official website mark, used with permission. All stay bundled like connector assets
// (no CDN at runtime). Keys are /v1/providers names; unknown names get no mark (the gallery
// falls back to a neutral monogram). PROVIDER_ORDER is the gallery order — recognition
// first, long tail behind the scroll fold.
import anthropic from "./logos/anthropic.svg";
import openai from "./logos/openai.svg";
import gemini from "./logos/gemini.svg";
import byteplus from "./logos/byteplus.svg";
import volcengine from "./logos/volcengine.svg";
import ollama from "./logos/ollama.svg";
import bedrock from "./logos/bedrock.svg";
import vertex from "./logos/vertex.svg";
@@ -27,6 +29,8 @@ export const PROVIDER_LOGOS: Record<string, string> = {
anthropic,
openai,
gemini,
ark: byteplus,
"ark-agent-plan-cn": volcengine,
meta,
ollama,
bedrock,
@@ -47,6 +51,8 @@ export const PROVIDER_ORDER = [
"anthropic",
"openai",
"gemini",
"ark",
"ark-agent-plan-cn",
"meta",
"ollama",
"bedrock",
@@ -0,0 +1 @@
<svg fill="none" shape-rendering="geometricPrecision" viewBox="0 0 27.9 22.01" xmlns="http://www.w3.org/2000/svg"><title>BytePlus</title><path fill="#0066FC" d="M17.196 9.442a.252.252 0 0 1-.419-.201V.612c0-.217-.263-.34-.418-.201L6.042 9.163a.252.252 0 0 1-.419-.201v-5.98a.31.31 0 0 0-.31-.31H.31a.31.31 0 0 0-.31.31v18.73c0 .216.263.34.418.2L10.72 13.16a.252.252 0 0 1 .418.202v8.644c0 .217.264.34.419.201l10.302-8.753a.252.252 0 0 1 .418.202v5.98c0 .17.14.31.31.31h5.003c.17 0 .31-.14.31-.31V.89c0-.217-.263-.341-.418-.202z"></path></svg>

After

Width:  |  Height:  |  Size: 543 B

@@ -0,0 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Volcengine</title><path d="M19.44 10.153l-2.936 11.586a.215.215 0 00.214.261h5.87a.215.215 0 00.214-.261l-2.95-11.586a.214.214 0 00-.412 0zM3.28 12.778l-2.275 8.96A.214.214 0 001.22 22h4.532a.212.212 0 00.214-.165.214.214 0 000-.097l-2.276-8.96a.214.214 0 00-.41 0z" fill="#00E5E5"></path><path d="M7.29 5.359L3.148 21.738a.215.215 0 00.203.261h8.29a.214.214 0 00.215-.261L7.7 5.358a.214.214 0 00-.41 0z" fill="#006EFF"></path><path d="M14.44.15a.214.214 0 00-.41 0L8.366 21.739a.214.214 0 00.214.261H19.9a.216.216 0 00.171-.078.214.214 0 00.044-.183L14.439.15z" fill="#006EFF"></path><path d="M10.278 7.741L6.685 21.736a.214.214 0 00.214.264h7.17a.215.215 0 00.214-.264L10.688 7.741a.214.214 0 00-.41 0z" fill="#00E5E5"></path></svg>

After

Width:  |  Height:  |  Size: 859 B

+52 -1
View File
@@ -17,6 +17,43 @@ from coworker.providers.openai_responses import (
convert_tools,
)
def test_responses_custom_base_url_reaches_sdk(monkeypatch):
captured: dict = {}
def fake_openai(**kwargs):
captured.update(kwargs)
return SimpleNamespace()
monkeypatch.setattr("openai.OpenAI", fake_openai)
provider = OpenAIResponsesProvider(
api_key="ark-key",
base_url="https://ark.example/api/v3/",
)
provider._ensure_client()
assert captured == {
"api_key": "ark-key",
"base_url": "https://ark.example/api/v3",
}
def test_stock_openai_responses_path_unchanged(monkeypatch):
"""Lockdown: stock OpenAI must not receive a vendor base URL."""
captured: dict = {}
def fake_openai(**kwargs):
captured.update(kwargs)
return SimpleNamespace()
monkeypatch.setattr("openai.OpenAI", fake_openai)
provider = OpenAIResponsesProvider(api_key="openai-key")
provider._ensure_client()
assert captured == {"api_key": "openai-key"}
# -- fakes ------------------------------------------------------------------------
@@ -254,7 +291,7 @@ def test_convert_tools_flattens_function_schemas():
# -- complete() ----------------------------------------------------------------------
def test_complete_text_turn_and_request_shape():
def test_complete_default_request_shape_PathsUnchanged():
fake = _FakeClient(response=_response([_message_item("hello")]))
provider = OpenAIResponsesProvider(client=fake)
turn = provider.complete(
@@ -304,6 +341,20 @@ def test_complete_usage_degrades_on_partial_or_missing_fields():
model="m", messages=[{"role": "user", "content": "hi"}]
)
assert turn2.usage is None
def test_complete_can_omit_reasoning_summary_but_keep_encrypted_content():
"""BytePlus accepts encrypted reasoning output but rejects reasoning.summary."""
fake = _FakeClient(response=_response([_message_item("hello")]))
provider = OpenAIResponsesProvider(client=fake, reasoning_summary=False)
provider.complete(model="m", messages=[{"role": "user", "content": "hi"}])
assert "reasoning" not in fake.kwargs
assert fake.kwargs["include"] == ["reasoning.encrypted_content"]
def test_reasoning_summary_capability_rejects_unknown_mode():
with pytest.raises(TypeError, match="reasoning_summary must be a bool"):
OpenAIResponsesProvider(client=SimpleNamespace(), reasoning_summary="auto")
def test_complete_parses_function_calls_with_call_ids():
+58
View File
@@ -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")
+115
View File
@@ -338,6 +338,121 @@ 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",
"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