oclaw/runtime/extensions/anthropic/api.py
oliver 4a23b715a2 重构仓库目录为统一的 runtime 分层并清理历史 openclaw 残留。
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。

Made-with: Cursor
2026-04-25 01:24:23 +08:00

150 lines
5.8 KiB
Python

from __future__ import annotations
from typing import Any, Callable
CLAUDE_CLI_BACKEND_ID = "claude-cli"
def is_claude_cli_provider(provider_id: str) -> bool:
return str(provider_id or "").strip().lower() == CLAUDE_CLI_BACKEND_ID
def build_anthropic_provider(_api=None) -> dict:
return {
"id": "anthropic",
"label": "Anthropic",
"docs_path": "/providers/models",
"hook_aliases": [CLAUDE_CLI_BACKEND_ID],
"env_vars": ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
# NOTE: Full auth, cli-backend, replay-policy, and stream wrappers are not ported yet.
}
def _parse_header_list(value: Any) -> list[str]:
if not isinstance(value, str):
return []
return [x.strip() for x in value.split(",") if x.strip()]
def _is_anthropic_oauth_api_key(api_key: Any) -> bool:
return isinstance(api_key, str) and "sk-ant-oat" in api_key
def _merge_anthropic_beta_header(headers: dict[str, str] | None, betas: list[str]) -> dict[str, str]:
merged = dict(headers or {})
existing_key = next((k for k in merged if str(k).lower() == "anthropic-beta"), None)
existing = _parse_header_list(merged.get(existing_key, "")) if existing_key else []
all_values = list(dict.fromkeys([*existing, *betas]))
merged[existing_key or "anthropic-beta"] = ",".join(all_values)
return merged
def resolve_anthropic_betas(extra_params: dict | None, model_id: str) -> list[str] | None:
extra_params = extra_params or {}
out: list[str] = []
configured = extra_params.get("anthropicBeta")
if isinstance(configured, str) and configured.strip():
out.append(configured.strip())
elif isinstance(configured, list):
out.extend([str(x).strip() for x in configured if str(x).strip()])
if extra_params.get("context1m") is True and str(model_id).lower().startswith(
("claude-opus-4", "claude-sonnet-4")
):
out.append("context-1m-2025-08-07")
out = list(dict.fromkeys(out))
return out or None
def create_anthropic_beta_headers_wrapper(base_stream_fn: Callable | None, betas: list[str]) -> Callable:
underlying = base_stream_fn or (lambda model, context, options=None: {"model": model, "context": context, "options": options or {}})
pi_defaults = ["fine-grained-tool-streaming-2025-05-14", "interleaved-thinking-2025-05-14"]
pi_oauth = ["claude-code-20250219", "oauth-2025-04-20", *pi_defaults]
def wrapped(model, context, options=None):
opts = dict(options or {})
is_oauth = _is_anthropic_oauth_api_key(opts.get("apiKey"))
requested_context1m = "context-1m-2025-08-07" in betas
effective_betas = [b for b in betas if not (is_oauth and requested_context1m and b == "context-1m-2025-08-07")]
all_betas = list(dict.fromkeys([*(pi_oauth if is_oauth else pi_defaults), *effective_betas]))
opts["headers"] = _merge_anthropic_beta_header(opts.get("headers"), all_betas)
return underlying(model, context, opts)
return wrapped
def _normalize_fast_mode(raw: Any) -> bool | None:
if isinstance(raw, bool):
return raw
if raw is None:
return None
key = str(raw).strip().lower()
if key in {"off", "false", "no", "0", "disable", "disabled", "normal"}:
return False
if key in {"on", "true", "yes", "1", "enable", "enabled", "fast"}:
return True
return None
def resolve_anthropic_fast_mode(extra_params: dict | None) -> bool | None:
extra_params = extra_params or {}
return _normalize_fast_mode(extra_params.get("fastMode", extra_params.get("fast_mode")))
def resolve_anthropic_service_tier(extra_params: dict | None) -> str | None:
extra_params = extra_params or {}
raw = extra_params.get("serviceTier", extra_params.get("service_tier"))
if isinstance(raw, str):
norm = raw.strip().lower()
if norm in {"auto", "standard_only"}:
return norm
return None
def create_anthropic_fast_mode_wrapper(base_stream_fn: Callable | None, enabled: bool) -> Callable:
underlying = base_stream_fn or (lambda model, context, options=None: {"model": model, "context": context, "options": options or {}})
def wrapped(model, context, options=None):
opts = dict(options or {})
if _is_anthropic_oauth_api_key(opts.get("apiKey")):
return underlying(model, context, opts)
payload = dict(opts.get("payload") or {})
payload["service_tier"] = "auto" if enabled else "standard_only"
opts["payload"] = payload
return underlying(model, context, opts)
return wrapped
def create_anthropic_service_tier_wrapper(base_stream_fn: Callable | None, service_tier: str) -> Callable:
underlying = base_stream_fn or (lambda model, context, options=None: {"model": model, "context": context, "options": options or {}})
def wrapped(model, context, options=None):
opts = dict(options or {})
if _is_anthropic_oauth_api_key(opts.get("apiKey")):
return underlying(model, context, opts)
payload = dict(opts.get("payload") or {})
payload["service_tier"] = service_tier
opts["payload"] = payload
return underlying(model, context, opts)
return wrapped
def wrap_anthropic_provider_stream(ctx: dict) -> Callable | None:
stream_fn = ctx.get("streamFn")
model_id = str(ctx.get("modelId") or "")
extra_params = ctx.get("extraParams") or {}
betas = resolve_anthropic_betas(extra_params, model_id)
service_tier = resolve_anthropic_service_tier(extra_params)
fast_mode = resolve_anthropic_fast_mode(extra_params)
wrapped = stream_fn
if betas:
wrapped = create_anthropic_beta_headers_wrapper(wrapped, betas)
if service_tier:
wrapped = create_anthropic_service_tier_wrapper(wrapped, service_tier)
if fast_mode is not None:
wrapped = create_anthropic_fast_mode_wrapper(wrapped, fast_mode)
return wrapped