Skip to content

Commit c302bbf

Browse files
authored
Merge pull request #1 from sena-labs/claude/pensive-hawking-d4ed27
fix: close HTTP response in pipes() and fix 9 documentation issues
2 parents 6c2a4b9 + 72fedbc commit c302bbf

10 files changed

Lines changed: 61 additions & 10 deletions

File tree

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ body:
2828
id: pipe-version
2929
attributes:
3030
label: Pipe Version
31-
placeholder: "e.g. 1.0.0"
31+
placeholder: "e.g. 1.2.0"
3232
validations:
3333
required: true
3434

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
## Checklist
1414

1515
- [ ] I have read the [CONTRIBUTING.md](../CONTRIBUTING.md) guidelines
16-
- [ ] All tests pass (`python test_pipe.py`234/234 ✓)
16+
- [ ] All tests pass (`python test_pipe.py`252/252 ✓)
1717
- [ ] I have added tests for new functionality (if applicable)
1818
- [ ] I have updated `CHANGELOG.md` under `[Unreleased]`
1919
- [ ] I have updated documentation (if applicable)

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,14 @@ htmlcov/
4141

4242
tmpclaude-*
4343
nul
44+
45+
# Auto Claude data directory
46+
.auto-claude/
47+
48+
# Auto Claude generated files
49+
.auto-claude-security.json
50+
.auto-claude-status
51+
.claude_settings.json
52+
.worktrees/
53+
.security-key
54+
logs/security/

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- **Icon sync: correct prefixed model IDs**`_sync_model_icons()` now discovers the pipe's `function_id` via `type(self).__module__` and writes DB records with the full prefixed ID (e.g. `openrouter_pipe.openai/gpt-4o`) matching what Open WebUI's frontend requests at `/models/model/profile/image`
1717
- **Streaming status event** — the "done" status event is now correctly emitted at the end of streaming responses (async generator wrapper replaces sync generator that could not `await`)
1818
- **Dead provider-icon code removed**`info.meta.profile_image_url` was included in model dicts returned by `pipes()` but Open WebUI ignores all fields except `id` and `name`; the field has been removed in favour of the new DB-sync approach
19+
- **`pipes()` response always closed** — added `finally: response.close()` to guarantee HTTP connections are returned to the session pool in all code paths (auth errors, JSON decode failures, unexpected exceptions)
1920

2021
## [1.2.0] - 2026-02-17
2122

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pip install -r requirements.txt
2929
### Running Tests
3030

3131
```bash
32-
python test_pipe.py # Unit tests (234 tests)
32+
python test_pipe.py # Unit tests (252 tests)
3333
python integration_test.py # Live API tests (requires OPENROUTER_API_KEY)
3434
```
3535

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ All settings are configurable via **Valves** in the Open WebUI admin panel. Ever
150150
| `FALLBACK_MODELS` | `OPENROUTER_FALLBACK_MODELS` | `""` | Fallback model IDs (comma-separated) |
151151
| `ENABLE_MIDDLE_OUT` | `OPENROUTER_ENABLE_MIDDLE_OUT` | `false` | Middle-out compression for long prompts |
152152
| `ENABLE_CACHE_CONTROL` | `OPENROUTER_ENABLE_CACHE_CONTROL` | `false` | Anthropic cache_control injection |
153+
| `SYNC_PROVIDER_ICONS` | `OPENROUTER_SYNC_ICONS` | `true` | Sync provider icons into Open WebUI's model database |
153154

154155
### Network
155156

@@ -215,7 +216,7 @@ It also removes `user` when sent as a dict (OWUI format) since OpenRouter expect
215216
OpenRouter-Pipe/
216217
├── openrouter_pipe.py # Main pipe source (install this in Open WebUI)
217218
├── function.json # Open WebUI community manifest (metadata, tags, categories)
218-
├── test_pipe.py # Unit test suite (234 tests)
219+
├── test_pipe.py # Unit test suite (252 tests)
219220
├── integration_test.py # Live API integration tests (47 tests)
220221
├── TESTING.md # Pre-release testing checklist
221222
├── SECURITY.md # Security policy and vulnerability reporting
@@ -274,7 +275,7 @@ Your API key is incorrect or malformed. Get a valid key from [openrouter.ai/keys
274275
<details>
275276
<summary><strong>"Rate limit exceeded (HTTP 429)"</strong></summary>
276277

277-
You're sending too many requests. Wait a moment and try again. Consider setting `MAX_RETRIES` to `2` or higher for automatic backoff.
278+
You're sending too many requests. Wait a moment and try again, or consider upgrading your OpenRouter plan. Note: `MAX_RETRIES` only retries on network timeouts and connection failures — HTTP rate limit errors are returned immediately without retry.
278279
</details>
279280

280281
<details>

SECURITY.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
## Supported Versions
44

55
| Version | Supported |
6-
|---------|-----------|| 1.2.x | Yes || 1.1.x | Yes |
6+
|---------|-----------|
7+
| 1.2.x | Yes |
8+
| 1.1.x | Yes |
79
| 1.0.x | Yes |
810
| < 1.0 | No |
911

TESTING.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Manual checklist to verify every Pipe feature before release.
1212
python test_pipe.py
1313
```
1414

15-
Must print **234/234 passed**. If any test fails, **do not release**.
15+
Must exit with `All tests passed! ✓` and `✗ Failed: 0`. If any test fails, **do not release**.
1616

1717
---
1818

@@ -41,7 +41,7 @@ Must print **234/234 passed**. If any test fails, **do not release**.
4141
| # | Action | Expected result |
4242
|---|--------|-----------------|
4343
| 3.1 | Select a model (e.g. `openai/gpt-4o`), type "Hello" with `stream: false` | The response appears all at once, correct text |
44-
| 3.2 | Select a reasoning model (e.g. `anthropic/claude-3.7-sonnet:thinking`) | The response contains `<think>...</think>` blocks followed by content |
44+
| 3.2 | Select a reasoning model (e.g. `deepseek/deepseek-r1`) | The response contains `<think>...</think>` blocks followed by content |
4545

4646
---
4747

@@ -105,7 +105,7 @@ Must print **234/234 passed**. If any test fails, **do not release**.
105105

106106
| # | Action | Expected result |
107107
|---|--------|-----------------|
108-
| 9.1 | Set `FALLBACK_MODELS = openai/gpt-4o, anthropic/claude-3.5-sonnet` | payload > `"models": ["openai/gpt-4o", "anthropic/claude-3.5-sonnet"]` |
108+
| 9.1 | While using `openai/gpt-4o` as primary, set `FALLBACK_MODELS = anthropic/claude-3.5-sonnet` | payload contains `"models": ["openai/gpt-4o", "anthropic/claude-3.5-sonnet"]` (primary model first, then fallbacks) |
109109
| 9.2 | Leave `FALLBACK_MODELS` empty | No `models` field in payload |
110110

111111
---
@@ -191,7 +191,7 @@ Must print **234/234 passed**. If any test fails, **do not release**.
191191
## Quick pre-release checklist
192192

193193
```
194-
[ ] python test_pipe.py → 234/234
194+
[ ] python test_pipe.py → 252 passed, 0 failed
195195
[ ] python integration_test.py → 47/47 ✓
196196
[ ] Empty API key → clear error
197197
[ ] Valid API key → 340+ models

openrouter_pipe.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ def pipes(self) -> List[dict]:
277277
return self._models_cache
278278

279279
headers = self._build_headers(include_content_type=False)
280+
response = None
280281
try:
281282
response = self._session.get(
282283
self.models_url, headers=headers, timeout=self.valves.REQUEST_TIMEOUT
@@ -313,6 +314,9 @@ def pipes(self) -> List[dict]:
313314
print(f"[OpenRouter Pipe] Model fetch error: {exc}")
314315
traceback.print_exc()
315316
return [{"id": "error", "name": f"Unexpected error: {exc}"}]
317+
finally:
318+
if response is not None:
319+
response.close()
316320

317321
provider_filter = self._parse_provider_filter()
318322
prefix = self.valves.MODEL_PREFIX or ""

test_pipe.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,38 @@ async def _test_pipe_stream() -> str:
10261026
_assert(models[0]["id"] == "error", "pipes provider no match: error id")
10271027
_assert("No models match" in models[0]["name"], "pipes provider no match: correct message")
10281028

1029+
# 15o. response.close() called via finally on auth-error branch (401)
1030+
_close_auth_resp = MagicMock()
1031+
_close_auth_resp.status_code = 401
1032+
_close_auth_resp.json.return_value = {"error": {"message": "Unauthorized"}}
1033+
pipe.valves = Pipe.Valves(OPENROUTER_API_KEY="bad-key")
1034+
pipe._models_cache = None
1035+
with patch.object(pipe._session, "get", return_value=_close_auth_resp):
1036+
pipe.pipes()
1037+
_assert(_close_auth_resp.close.call_count == 1, "pipes response.close(): called after 401 auth error")
1038+
1039+
# 15p. response.close() called via finally when response.json() raises (JSONDecodeError)
1040+
_close_json_err_resp = MagicMock()
1041+
_close_json_err_resp.status_code = 200
1042+
_close_json_err_resp.raise_for_status = MagicMock()
1043+
_close_json_err_resp.json.side_effect = ValueError("No JSON object could be decoded")
1044+
pipe.valves = Pipe.Valves(OPENROUTER_API_KEY="test-key")
1045+
pipe._models_cache = None
1046+
with patch.object(pipe._session, "get", return_value=_close_json_err_resp):
1047+
pipe.pipes()
1048+
_assert(_close_json_err_resp.close.call_count == 1, "pipes response.close(): called after JSON decode error")
1049+
1050+
# 15q. response.close() called via finally on success path
1051+
_close_ok_resp = MagicMock()
1052+
_close_ok_resp.status_code = 200
1053+
_close_ok_resp.raise_for_status = MagicMock()
1054+
_close_ok_resp.json.return_value = {"data": [{"id": "openai/gpt-4o", "name": "GPT-4o"}]}
1055+
pipe.valves = Pipe.Valves(OPENROUTER_API_KEY="test-key")
1056+
pipe._models_cache = None
1057+
with patch.object(pipe._session, "get", return_value=_close_ok_resp):
1058+
pipe.pipes()
1059+
_assert(_close_ok_resp.close.call_count == 1, "pipes response.close(): called after successful listing")
1060+
10291061
# ── 16. Valve json_schema_extra ──────────────────────────────────────────────
10301062

10311063
_section("16. Valve json_schema_extra")

0 commit comments

Comments
 (0)