mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
## Description This change adds bounded timeout and circuit-breaker behavior so OpenClaw can degrade safely when Headroom or the upstream stream stalls, while returning structured proxy errors instead of hanging. Closes #638 by improving OpenClaw/proxy resilience when the Headroom proxy stalls or Anthropic resets a stream. The PR adds proxy-side handling for `httpx.RemoteProtocolError`, returns structured 502 responses for otherwise unhandled proxy middleware errors, and adds OpenClaw plugin timeout/circuit-breaker fallback behavior. ## Type of Change - [x] Bug fix - [ ] New feature - [x] Documentation - [ ] Refactor - [x] Tests only ## Changes Made - Added OpenClaw plugin per-request compression timeout and circuit breaker fallback. - Cleared timeout timers after successful or failed compression so successful calls do not leave pending timers. - Added a focused Vitest regression for timeout cleanup. - Added `contracts.tools` for `headroom_retrieve` without whole-file manifest reformatting. - Added proxy handling for mid-stream `httpx.RemoteProtocolError` and structured 502 fallback behavior. - Documented the new OpenClaw resilience configuration fields. ## Testing - [x] Unit tests - [x] Integration-style proxy tests - [x] Typecheck/build - [ ] Manual testing ### Test Output ```text cd plugins/openclaw && npm test Test Files 6 passed (6), Tests 55 passed (55) cd plugins/openclaw && npm run typecheck passed cd plugins/openclaw && npm run build Build success UV_SKIP_WHEEL_FILENAME_CHECK=1 uv run --with pytest --with pytest-asyncio --with fastapi --with httpx --with uvicorn --with h2 python -m pytest tests/test_proxy_streaming_resilience.py -q 24 passed in 2.16s ``` ## Real Behavior Proof - Environment: Windows 11, Node/npm from local plugin worktree, Python 3.13.3, focused local worktree for PR #639. - Exact command / steps: Installed plugin dependencies, ran OpenClaw plugin tests/typecheck/build, and ran the proxy streaming resilience suite with required async/FastAPI/httpx extras. - Observed result: Plugin tests, typecheck, build, and proxy resilience tests all passed. - Not tested: Live OpenClaw gateway session in this pass; original reporter previously verified patched files in a container and OpenClaw degraded/recovered cleanly. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review --------- Co-authored-by: Sergei Baikin <sergei.baikin@fotograf.de> Co-authored-by: JerrettDavis <mxjerrett@gmail.com> Co-authored-by: JD Davis <jd@jds-macbook-air.tail2a279.ts.net>
125 lines
3.8 KiB
JSON
125 lines
3.8 KiB
JSON
{
|
|
"id": "headroom",
|
|
"kind": "context-engine",
|
|
"uiHints": {
|
|
"proxyUrl": {
|
|
"label": "Proxy URL",
|
|
"help": "Optional. URL Headroom proxy (example: http://127.0.0.1:8787 or https://headroom.example.com). Configured URLs probe-gated before provider routing. Auto-start opt-in only works local addresses."
|
|
},
|
|
"proxyPort": {
|
|
"label": "Proxy Port",
|
|
"help": "Default port used for auto-detect/auto-start when proxyUrl is not set (default: 8787)."
|
|
},
|
|
"pythonPath": {
|
|
"label": "Python Path",
|
|
"help": "Optional explicit python executable for python fallback launcher (for example: python, python3, py, or full path)."
|
|
},
|
|
"retryMaxAttempts": {
|
|
"label": "Retry Max Attempts",
|
|
"help": "Optional maximum number of upstream retry attempts for connection/read/5xx failures when the plugin auto-starts a local Headroom proxy. Lower values fail faster for interactive chat."
|
|
},
|
|
"connectTimeoutSeconds": {
|
|
"label": "Connect Timeout Seconds",
|
|
"help": "Optional upstream connection timeout for the auto-started local Headroom proxy. Lower values surface network failures sooner."
|
|
},
|
|
"requestTimeoutMs": {
|
|
"label": "Compression Request Timeout",
|
|
"help": "Maximum milliseconds to wait for one compression request before returning the original messages."
|
|
},
|
|
"circuitBreakerThreshold": {
|
|
"label": "Circuit Breaker Threshold",
|
|
"help": "Consecutive compression failures before temporarily bypassing Headroom."
|
|
},
|
|
"circuitBreakerCooldownMs": {
|
|
"label": "Circuit Breaker Cooldown",
|
|
"help": "Milliseconds to bypass compression after the circuit breaker opens."
|
|
},
|
|
"routeCodexViaProxy": {
|
|
"label": "Route OpenAI Codex Via Headroom",
|
|
"help": "When enabled, OpenClaw will use the active Headroom proxy as the in-memory upstream base URL for the built-in openai-codex provider so provider traffic flows through Headroom."
|
|
},
|
|
"gatewayProviderIds": {
|
|
"label": "Gateway Provider IDs",
|
|
"help": "Optional list of OpenClaw provider ids to route through the active Headroom proxy in memory. Friendly aliases codex, claude, copilot, and gemini are also accepted. When set, this overrides the default openai-codex-only routing."
|
|
}
|
|
},
|
|
"configSchema": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"enabled": {
|
|
"type": "boolean"
|
|
},
|
|
"proxyUrl": {
|
|
"type": "string",
|
|
"pattern": "^https?:\\/\\/.+(:\\d+)?\\/?$"
|
|
},
|
|
"proxyPort": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"maximum": 65535,
|
|
"default": 8787
|
|
},
|
|
"pythonPath": {
|
|
"type": "string"
|
|
},
|
|
"autoStart": {
|
|
"type": "boolean",
|
|
"default": false
|
|
},
|
|
"startupTimeoutMs": {
|
|
"type": "integer",
|
|
"minimum": 1000,
|
|
"maximum": 120000,
|
|
"default": 20000
|
|
},
|
|
"retryMaxAttempts": {
|
|
"type": "integer",
|
|
"minimum": 1
|
|
},
|
|
"connectTimeoutSeconds": {
|
|
"type": "integer",
|
|
"minimum": 1
|
|
},
|
|
"requestTimeoutMs": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"default": 30000
|
|
},
|
|
"circuitBreakerThreshold": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"default": 3
|
|
},
|
|
"circuitBreakerCooldownMs": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"default": 60000
|
|
},
|
|
"routeCodexViaProxy": {
|
|
"type": "boolean",
|
|
"default": true
|
|
},
|
|
"gatewayProviderIds": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"default": []
|
|
}
|
|
}
|
|
},
|
|
"capabilities": {
|
|
"network": {
|
|
"allow": [
|
|
"http://*:*",
|
|
"https://*:*"
|
|
]
|
|
}
|
|
},
|
|
"contracts": {
|
|
"tools": [
|
|
"headroom_retrieve"
|
|
]
|
|
}
|
|
}
|