Serwer proxy dla API Claude poprzez sesję webową: integracja z OpenClaw
Anthropic zablokowała używanie Claude w aplikacjach innych firm, w tym openclaw. Subskrybenci Pro (100 USD/mies.) otrzymali rekompensatę 100 USD na dodatkowe użycie, ale nie rozwiązuje to problemu integracji z agentami. Rozwiązaniem jest serwer proxy, emulujący sesję przeglądarkową Claude.ai z API w formacie OpenAI. Pozwala używać Claude Sonnet/Opus w openclaw z obsługą tool use.
Serwer w Pythonie z curl_cffi dla odcisku TLS Chrome. Ciasteczka i device-id pobierane są z aktywnej sesji. Tworzona jest nowa rozmowa dla każdego zapytania, completion strumieniowane przez SSE.
Podstawowa implementacja sesji i nagłówków
Sesja maskowana jako Chrome z ciasteczkami z przeglądarki. Testowanie przez przeglądarkę antydetekcyjną camoufox.
def make_session() -> cf_requests.Session:
"""Utwórz nową sesję curl_cffi z odciskiem TLS Chrome."""
cookies = get_cookies()
session = cf_requests.Session(impersonate=IMPERSONATE)
session.cookies.update(cookies)
return session
def get_headers(cookies: dict) -> dict:
return {
"User-Agent": (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
),
"Origin": "https://claude.ai",
"Referer": "https://claude.ai/new",
"anthropic-device-id": cookies.get("anthropic-device-id", ""),
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
}
Logika zapytania completion
- POST na
/api/organizations/{org_id}/chat_conversationsdo utworzenia rozmowy. - POST na
/chat_conversations/{conv_id}/completionztext/event-stream.
def claude_complete(
prompt: str,
internal_model: str,
) -> Generator[str, None, None]:
"""Generator synchroniczny zwracający linie SSE z completion claude.ai."""
cookies = get_cookies()
org_id = cookies["lastActiveOrg"]
headers = get_headers(cookies)
session = make_session()
# 1. Utwórz rozmowę
r = session.post(
f"https://claude.ai/api/organizations/{org_id}/chat_conversations",
json={"uuid": None, "name": ""},
headers=headers,
timeout=15,
)
r.raise_for_status()
conv_id = r.json()["uuid"]
log.info(f"Rozmowa utworzona: {conv_id} model={internal_model}")
# 2. Strumieniuj completion
payload = {
"prompt": prompt,
"model": internal_model,
"timezone": "Europe/Warsaw",
"attachments": [],
"files": [],
}
r2 = session.post(
f"https://claude.ai/api/organizations/{org_id}/chat_conversations/{conv_id}/completion",
json=payload,
headers={**headers, "Accept": "text/event-stream"},
stream=True,
timeout=60,
)
r2.raise_for_status()
for line in r2.iter_lines():
yield line
Serwer API na aiohttp: trasy /v1/models, /v1/chat/completions, /health. Uruchomienie na 127.0.0.1:8787.
def make_app() -> web.Application:
app = web.Application()
app.router.add_get("/v1/models", handle_models)
app.router.add_get("/health", handle_health)
app.router.add_post("/v1/chat/completions", handle_chat_completions)
return app
Integracja w OpenClaw
Konfiguracja dostawcy:
"claude-web": {
"baseUrl": "http://127.0.0.1:8787/v1",
"apiKey": "dummy",
"api": "openai-completions",
"models": [
{
"id": "claude-sonnet-4-6-web",
"name": "Claude Sonnet 4.6 (Web)",
"reasoning": false,
"input": ["text"],
"cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0},
"contextWindow": 200000,
"maxTokens": 32000
}
]
}
Generacja tekstowa działa od razu. Tool use wymaga dopracowania.
Obsługa tool use: wstrzyknięcie promptu
Prompt systemowy zmusza Claude do wyprowadzania tool calls w formacie XML:
TOOL_SYSTEM_PROMPT = """Masz dostęp do narzędzi. Kiedy potrzebujesz wywołać narzędzie, wyprowadź TYLKO blok tool call – bez wyjaśnień, bez tekstu przed lub po:
<tool_call>
{{"name": "nazwa_narzędzia_tutaj", "arguments": {{"param1": "wartość1", "param2": "wartość2"}}}}
</tool_call>
ZASADY:
- JSON wewnątrz <tool_call> musi być poprawny – bez przecinków końcowych, bez komentarzy
- Możesz wyprowadzić wiele bloków <tool_call> w sekwencji
- Po otrzymaniu bloków <tool_result> kontynuuj odpowiedź normalnie
- Jeśli nie wywołujesz narzędzia, odpowiadaj normalnie bez tagów <tool_call>
Dostępne narzędzia:
{tools_json}"""
Parsowanie przez regex, autofix błędów JSON (usuwanie przecinków końcowych, komentarzy).
Końcowa obróbka tool calls i wyników
- Parser wyodrębnia bloki
<tool_call>, naprawia JSON, konwertuje do formatu OpenAI. - Tool results wstrzykiwane jako XML
<tool_result id="...">treść</tool_result>w wiadomościach użytkownika.
def parse_tool_calls(text: str) -> tuple[list[dict], str]:
tool_calls = []
for match in TOOL_CALL_RE.finditer(text):
raw_json = match.group(1).strip()
obj = None
try:
obj = json.loads(raw_json)
except json.JSONDecodeError:
fixed = _try_fix_json(raw_json)
obj = json.loads(fixed)
if obj:
tool_calls.append({
"id": f"call_{uuid.uuid4().hex[:12]}",
"type": "function",
"function": {
"name": obj.get("name", ""),
"arguments": json.dumps(obj.get("arguments", {})),
},
})
remaining = TOOL_CALL_RE.sub("", text).strip()
return tool_calls, remaining
def inject_tool_results(messages: list[dict], tools: list[dict]) -> list[dict]:
# Konwersja OpenAI tool_calls/results do XML dla Claude
# ... (logika batching i generacji XML)
Co jest ważne
- Pełna kompatybilność: OpenAI API z tool use dla openclaw/Claude Sonnet 4.6 (context 200k).
- Antydetekcja: curl_cffi + rzeczywiste ciasteczka/device-id, TLS Chrome.
- Autofix: Obsługa typowych błędów JSON Claude (przecinki, komentarze).
- Streaming: SSE z web API, nowa rozmowa per request.
- Limity: Działa w ramach subskrypcji Pro bez extra usage.
Ograniczenia i poprawki
Rozwiązanie zależy od stabilności web API Claude.ai. Możliwe ryzyka:
- Captcha na stronie.
- Ograniczenie sesji czasowo.
- Zmiany endpointów.
Dla obejścia: aktualizacja ciasteczek przez automatyzację, rotacja device-id, obsługa błędów.
— Editorial Team
Brak komentarzy.