headroom/plugins/openclaw/openclaw.plugin.json
Sergei Baikin 6576ef639c
fix(openclaw-plugin): circuit breaker + per-request timeout for proxy resilience (#639)
## 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>
2026-08-13 11:52:01 -05:00

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"
]
}
}