From 4f4f866f0a5686fc7e1a51d4a39527dcc6801743 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Mon, 15 Jun 2026 17:41:52 +0600 Subject: [PATCH] =?UTF-8?q?[2026-06-15]=20claude-proxy-node-internals=20?= =?UTF-8?q?=E2=80=94=20add=20alternatives=20comparison=20table=20and=20ana?= =?UTF-8?q?lysis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../claude-proxy-node-internals.md | 58 +++++++++++++++++-- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/personal/projects/personal-os/claude-proxy-node-internals.md b/personal/projects/personal-os/claude-proxy-node-internals.md index f9248609..96498bc4 100644 --- a/personal/projects/personal-os/claude-proxy-node-internals.md +++ b/personal/projects/personal-os/claude-proxy-node-internals.md @@ -120,16 +120,62 @@ Hermes ── парсит tool_use ── выполняет через сво **Python wrapper (`claude-code-openai-wrapper`) этот flow поддерживает** — у него есть `stop_after_first_assistant` и `disallowed_tools`. Именно поэтому он активен, а Node.js — fallback. +## Обзор альтернатив + +| Проект | Язык | Проблема 1 (orphaned) | Проблема 2 (вывод CLI) | Проблема 3 (caller-dispatched tools) | +|--------|------|-----------------------|------------------------|--------------------------------------| +| `openclaw-claude-proxy` (порт 3456) | TypeScript | ❌ — pool без SIGTERM cleanup | ❌ — stderr не форвардится | ❌ — stream-json сломан с CLI ≥ 2.1.141 | +| `claude-code-openai-wrapper` (RichardAtCT, порт 8090) | Python | ❌ — uvicorn без tini | ❌ — SDK pipe без логирования | ✅ — `stop_after_first_assistant` + `disallowed_tools` | +| `claude-code-openai-wrapper` (clebermasters) | Rust | ❌ — tokio без reaper | ❌ — pipe | ❌ — CLI сам исполняет инструменты | +| `claude-max-api-proxy` (mnemon-dev) | TypeScript | ❌ | ❌ | ❌ — родительский проект openclaw | +| **Hermes `provider: claude-code`** | встроен | N/A | N/A | ✅ — но упирается в rate limit OAuth API | + +**Ни один существующий проект не решает все три проблемы одновременно.** + +### `openclaw-claude-proxy` — форки и состояние + +Репозиторий [mehdic/openclaw-claude-proxy](https://github.com/mehdic/openclaw-claude-proxy): +- **v1.0.8** (May 2026) — последний релиз +- 2 форка (habibtalik, ppcvote), 0 звёзд +- Нет открытых issues +- **subprocess/manager.ts** (311 строк): `spawn()` + `EventEmitter`, есть `disallowedTools` в опциях, но они работают только в `--print` режиме. В `stream-json` режиме multi-turn сломан протоколом CLI ≥ 2.1.141. +- `kill()` отправляет SIGTERM на один процесс, но **глобального cleanup при SIGTERM/SIGKILL родителя нет**. + +### `clebermasters/claude-code-openai-wrapper` (Rust) + +Альтернатива, переписанная на Rust: +- Один статический бинарник 4.7 MB, без зависимостей времени выполнения +- Использует `tokio::process::Command` + `--print --output-format stream-json` +- Проблемы те же — запускает `claude` как subprocess, CLI сам исполняет инструменты +- Проект новый — 0 issues, 0 форков + +### `claude-code-openai-wrapper` (RichardAtCT, Python) + +Единственный проект, решающий **проблему 3** (caller-dispatched tools): +- Логика: `stop_after_first_assistant` останавливает итерацию после первого assistant-сообщения, `disallowed_tools=['Bash','Read',...]` не даёт CLI самому исполнять инструменты +- Возвращает tool_use блоки как OpenAI `tool_calls` для исполнения Hermes'ом +- Проблема 1 (orphaned): если запущен через uvicorn без `--init`, при SIGKILL дочерний `claude` процесс остаётся orphan. SDK `atexit` не срабатывает при SIGKILL. +- Проблема 2 (вывод): SDK читает stdout `claude` через pipe → JSON → memory channel. Человеческий вывод не транслируется. stderr CLI читается тихо в `_handle_stderr` и никуда не выводится. + ## Предложенные подходы -### Против orphaned процессов +### Против orphaned процессов (все проекты) 1. **Docker `--init` флаг:** запускать контейнер с `init: true` в docker-compose (или `docker run --init`). Использует `tini` как PID 1 — корректно форвардит SIGTERM и перезахоранивает orphan'ов. -2. **SIGTERM handler в Node.js:** добавить `process.on('SIGTERM', ...)` который форсированно убивает весь child process pool перед exit. -3. **Health check + restart policy:** не `always`, а `unless-stopped` с `--time=30` grace period. +2. **SIGTERM handler:** в Node.js — `process.on('SIGTERM', pool.killAll())`, в Python — `uvicorn` lifespan shutdown c force kill. +3. **Health check + restart policy:** `unless-stopped` с `--time=30` grace period. ### Против отсутствия вывода CLI -1. **Проброс stderr:** pipe'ить stderr `claude` в stderr proxy — видно в `docker logs` или launchd логах. -2. **Режим `print`:** переключить `CLAUDE_PROXY_RUNTIME=print` — теряется производительность stream-json, но логов больше. -3. **Node.js `stream-json` мониторинг:** логировать каждую N-ную JSON строку из stdout claude для отладки. +1. **Python SDK:** добавить `stderr` callback в `ClaudeAgentOptions`, который пишет в `stderr` uvicorn → видно в `docker logs`. +2. **Node.js proxy:** форвардить stderr `claude` в stderr родителя. +3. **Логирование JSON:** логировать каждую N-ную строку из stdout `claude` для отладки. + +### Против изоляции инструментов (caller-dispatched tools) + +Единственное работающее решение — **Python wrapper** (`claude-code-openai-wrapper` RichardAtCT) с: +- `stop_after_first_assistant = True` +- `disallowed_tools = ['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep']` +- `permission_mode = 'bypassPermissions'` + +Для полного решения всех трёх проблем нужно взять Python wrapper, запустить в Docker с `init: true` и добавить проброс stderr из SDK.