mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 07:03:15 +08:00
本次迁移将网关/通道/工具/技能/脚本与协议资源集中到新结构,统一路径常量与脚本转发机制,减少顶层噪音并保证运行与测试行为一致。 Made-with: Cursor
150 lines
5.8 KiB
Python
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
|