Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/tau_ai/anthropic.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
from tau_ai.events import AssistantMessageEvent
from tau_ai.http import create_async_client
from tau_ai.http_errors import provider_http_error_message
from tau_ai.openai_compatible import _apply_session_affinity_headers
from tau_ai.provider import CancellationToken
from tau_ai.retry import (
RETRYABLE_TRANSPORT_ERRORS,
Expand Down Expand Up @@ -105,9 +106,13 @@ def stream_response(
session_id: str | None = None,
) -> AsyncIterator[AssistantMessageEvent]:
"""Stream one response as Pi-compatible assistant message events."""
del session_id
raw = self._stream_provider_events(
model=model, system=system, messages=messages, tools=tools, signal=signal
model=model,
system=system,
messages=messages,
tools=tools,
signal=signal,
session_id=session_id,
)
return canonicalize_provider_stream(
raw, api="anthropic-messages", provider="anthropic", model=model
Expand All @@ -121,6 +126,7 @@ def _stream_provider_events(
messages: list[AgentMessage],
tools: list[AgentTool],
signal: CancellationToken | None = None,
session_id: str | None = None,
) -> AsyncIterator[ProviderEvent]:
"""Stream one Anthropic response as provider-neutral events."""

Expand Down Expand Up @@ -167,6 +173,7 @@ async def iterator() -> AsyncIterator[ProviderEvent]:
headers.setdefault("Authorization", f"Bearer {api_key}")
else:
headers["x-api-key"] = api_key
_apply_session_affinity_headers(headers, session_id, self._session_affinity_format())
url = f"{base_url.rstrip('/')}/messages"

attempt = 0
Expand Down Expand Up @@ -384,6 +391,13 @@ def _should_retry(self, attempt: int, *, status_code: int | None = None) -> bool
return False
return status_code is None or status_code in {408, 409, 425, 429} or status_code >= 500

def _session_affinity_format(self) -> str | None:
compat = self._config.compat or {}
if compat.get("sendSessionAffinityHeaders") is not True:
return None
value = compat.get("sessionAffinityFormat")
return value if isinstance(value, str) and value else None


_TRANSIENT_ANTHROPIC_STREAM_ERROR_TYPES = frozenset(
{
Expand Down
1 change: 1 addition & 0 deletions src/tau_ai/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class AnthropicConfig:
bearer_auth: bool = False
base_url: str = DEFAULT_ANTHROPIC_BASE_URL
headers: Mapping[str, str] | None = None
compat: Mapping[str, JSONValue] = field(default_factory=dict)
timeout_seconds: float = DEFAULT_OPENAI_COMPATIBLE_TIMEOUT_SECONDS
max_retries: int = DEFAULT_OPENAI_COMPATIBLE_MAX_RETRIES
max_retry_delay_seconds: float = DEFAULT_OPENAI_COMPATIBLE_MAX_RETRY_DELAY_SECONDS
Expand Down
3 changes: 3 additions & 0 deletions src/tau_ai/openai_compatible.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,9 @@ def _apply_session_affinity_headers(
if affinity_format == "openrouter":
headers["x-session-id"] = session_id
return
if affinity_format == "opencode":
headers["x-opencode-session"] = session_id
return
if affinity_format == "openai":
headers["session_id"] = session_id

Expand Down
2 changes: 2 additions & 0 deletions src/tau_coding/catalog_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class _CatalogProvider(BaseModel):
default_model: _NonEmptyString
docs_url: _NonEmptyString
api: ProviderApi | None = None
anthropic_auth: _NonEmptyString | None = None
context_windows: dict[_NonEmptyString, _PositiveInt] | None = None
headers: dict[_NonEmptyString, _NonEmptyString] = {}
compat: dict[_NonEmptyString, Any] = {}
Expand Down Expand Up @@ -470,6 +471,7 @@ def _entry_from_provider(provider: _CatalogProvider, *, source: str) -> Provider
default_model=provider.default_model,
docs_url=provider.docs_url,
api=provider.api,
anthropic_auth=provider.anthropic_auth,
context_windows=context_windows or None,
headers=dict(provider.headers),
compat=_json_object(provider.compat, f"{prefix}.compat"),
Expand Down
77 changes: 69 additions & 8 deletions src/tau_coding/data/catalog.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6590,68 +6590,129 @@ kind = "openai-compatible"
base_url = "https://opencode.ai/zen/go/v1"
api_key_env = "OPENCODE_API_KEY"
credential_name = "opencode-go"
models = ["deepseek-v4-flash", "deepseek-v4-pro", "glm-5.1", "glm-5.2", "kimi-k2.6", "kimi-k2.7-code", "mimo-v2.5", "mimo-v2.5-pro", "minimax-m2.7", "minimax-m3", "qwen3.6-plus", "qwen3.7-max", "qwen3.7-plus"]
models = ["deepseek-v4-flash", "deepseek-v4-flash-vision-exp", "deepseek-v4-pro", "deepseek-v4.1-flash", "glm-5.2", "glm-5.3", "glm-5.3-flash", "gpt-5.6-luna", "gpt-6-luna", "grok-4.6", "grok-4.7", "hy3", "hy4-preview", "kimi-k2.6", "kimi-k2.7-code", "kimi-k3", "longcat-2.0", "longcat-2.5-preview-free", "mimo-v2.5", "mimo-v2.5-pro", "mimo-v2.6-flash", "mimo-v2.6-pro", "minimax-m2.7", "minimax-m3", "muse-spark-1.2-contributor", "muse-spark-1.3-contributor", "qwen3.7-plus", "qwen3.8-flash", "qwen3.8-max", "space-bunny"]
default_model = "kimi-k2.7-code"
docs_url = "https://opencode.ai/docs/go"
api = "openai-completions"
anthropic_auth = "api-key"
compat = { sendSessionAffinityHeaders = true, sessionAffinityFormat = "opencode" }
thinking_levels = ["off", "minimal", "low", "medium", "high", "xhigh"]
thinking_default = "medium"
thinking_parameter = "reasoning_effort"

[providers.context_windows]
"deepseek-v4-flash" = 1000000
"deepseek-v4-pro" = 1000000
"glm-5.1" = 202752
"glm-5.2" = 202752
"kimi-k2.6" = 262144
"kimi-k2.7-code" = 262144
"mimo-v2.5" = 262144
"mimo-v2.5-pro" = 262144
"minimax-m2.7" = 204800
"minimax-m3" = 204800
"qwen3.6-plus" = 1000000
"qwen3.7-max" = 1000000
"qwen3.7-plus" = 1000000

[providers.model_metadata."deepseek-v4-flash"]
input = ["text"]

[providers.model_metadata."deepseek-v4-flash-vision-exp"]
input = ["text", "image"]

[providers.model_metadata."deepseek-v4-pro"]
input = ["text"]

[providers.model_metadata."glm-5.1"]
[providers.model_metadata."deepseek-v4.1-flash"]
input = ["text"]

[providers.model_metadata."glm-5.2"]
input = ["text"]

[providers.model_metadata."glm-5.3"]
input = ["text"]

[providers.model_metadata."glm-5.3-flash"]
input = ["text"]

[providers.model_metadata."gpt-5.6-luna"]
api = "openai-responses"
input = ["text", "image"]

[providers.model_metadata."gpt-6-luna"]
api = "openai-responses"
input = ["text", "image"]

[providers.model_metadata."grok-4.6"]
api = "openai-responses"
input = ["text", "image"]

[providers.model_metadata."grok-4.7"]
api = "openai-responses"
input = ["text", "image"]

[providers.model_metadata."hy3"]
input = ["text"]

[providers.model_metadata."hy4-preview"]
input = ["text"]

[providers.model_metadata."kimi-k2.6"]
input = ["text", "image"]

[providers.model_metadata."kimi-k2.7-code"]
input = ["text", "image"]

[providers.model_metadata."kimi-k3"]
input = ["text", "image"]

[providers.model_metadata."longcat-2.0"]
input = ["text"]

[providers.model_metadata."longcat-2.5-preview-free"]
input = ["text"]

[providers.model_metadata."mimo-v2.5"]
input = ["text", "image"]

[providers.model_metadata."mimo-v2.5-pro"]
input = ["text"]

[providers.model_metadata."mimo-v2.6-flash"]
input = ["text", "image"]

[providers.model_metadata."mimo-v2.6-pro"]
input = ["text"]

[providers.model_metadata."minimax-m2.7"]
api = "anthropic-messages"
input = ["text"]

[providers.model_metadata."minimax-m3"]
api = "anthropic-messages"
input = ["text", "image"]

[providers.model_metadata."qwen3.6-plus"]
input = ["text", "image"]
[providers.model_metadata."muse-spark-1.2-contributor"]
api = "openai-responses"
input = ["text"]

[providers.model_metadata."qwen3.7-max"]
[providers.model_metadata."muse-spark-1.3-contributor"]
api = "openai-responses"
input = ["text"]

[providers.model_metadata."qwen3.7-plus"]
api = "anthropic-messages"
input = ["text", "image"]

[providers.model_metadata."qwen3.8-flash"]
api = "anthropic-messages"
input = ["text", "image"]

[providers.model_metadata."qwen3.8-max"]
api = "anthropic-messages"
input = ["text", "image"]

[providers.model_metadata."space-bunny"]
input = ["text"]

[[providers]]
name = "opencode"
display_name = "OpenCode Zen"
Expand Down
Loading