diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 065ef31..17b30d7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -28,7 +28,7 @@ body: id: pipe-version attributes: label: Pipe Version - placeholder: "e.g. 1.2.0" + placeholder: "e.g. 1.3.0" validations: required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 57e663d..6f14438 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -25,7 +25,7 @@ ## Testing -- [ ] All unit tests pass (`python test_pipe.py` — 322/322 ✓) +- [ ] All unit tests pass (`python test_pipe.py` — 431/431 ✓) - [ ] New tests added for the changes - [ ] Integration tests pass (`python integration_test.py`) — if applicable - [ ] `CHANGELOG.md` updated under `[Unreleased]` diff --git a/CHANGELOG.md b/CHANGELOG.md index d8cf4dc..d0c3db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,29 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.3.0] — 2026-05-07 + ### Added - **Automatic provider-icon sync** — new `_sync_model_icons()` method writes provider icons directly into Open WebUI's Models database so they appear in the UI; controlled by the `SYNC_PROVIDER_ICONS` valve (default: enabled). Models with a manually-set icon are never overwritten - **`_is_owui_managed_icon()` helper** — distinguishes OWUI-default icons (`data:` URLs) and our own provider icons from user-set custom icons, enabling safe icon updates without clobbering user customisations - -### Fixed - -- **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` -- **Icon sync: icons now actually appear in the UI** — five bugs prevented provider icons from ever showing after the first pipe load: - - *Wrong skip condition* — `if existing_icon:` skipped any model with *any* icon (including the generic `data:` SVG that OWUI assigns by default), so provider icons were never applied; fixed to skip only user-set custom URLs - - *Race condition* — `_sync_model_icons()` was called before `pipes()` returned, i.e. before OWUI registered the models; OWUI then overwrote the early insert with its own default icon; fixed by also calling `_sync_model_icons()` on cache-hit paths (until all models are confirmed synced) - - *Exception swallowed retry* — DB errors added the model to `_icons_synced` anyway, permanently preventing retry; removed the erroneous add - - *Insert marked as synced prematurely* — after `insert_new_model` the model was marked synced even though OWUI could overwrite it; the insert path no longer updates `_icons_synced` - - *User params clobbered* — `update_model_by_id` used an empty `ModelParams()`, erasing user-configured temperature/system-prompt/etc.; now preserves `existing.params` -- **Icon sync: `function_id` cached at init** — `type(self).__module__` is evaluated once in `__init__` instead of on every `_sync_model_icons()` call -- **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`) -- **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 -- **`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) - -## [1.2.0] — 2026-02-17 - -### Added - +- **Audio output handling** — models that return audio (e.g. `openai/gpt-4o-audio-preview`) now have their transcript surfaced as text in both streaming and non-streaming responses +- **Image output handling** — models that return images (e.g. `google/gemini-2.5-flash-image-preview`) now embed valid HTTP/HTTPS image URLs as markdown, with a leading blank-line separator and URL validation to drop unsafe schemes +- **Token usage and cost display** — non-stream responses append a "Tokens: X in / Y out · Cost: $Z" footer when the OpenRouter response includes `usage` data - **Connection pooling** via `requests.Session` for better performance across multiple API calls - **Model list caching** with 5-minute TTL and valve-fingerprint invalidation — avoids redundant API calls when reopening the model selector - **Exponential backoff with jitter** on transient errors (Timeout, ConnectionError) — `min(2^attempt + random, 30s)` @@ -55,9 +41,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `FREE_ONLY` pricing comparison uses `float()` instead of string comparison - Model cache key includes `MODEL_PREFIX` to prevent stale results after prefix changes - Removed unused `_API_PATH_AUTH` constant and `auth_url` property +- Provider-icon catalogue trimmed to 13 verified providers (was previously documented as 22 but only 13 were ever defined in `_PROVIDER_ICONS`); tilde model filtering and model-ID stripping corrected at the same time ### Fixed +- **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` +- **Icon sync: icons now actually appear in the UI** — five bugs prevented provider icons from ever showing after the first pipe load: + - *Wrong skip condition* — `if existing_icon:` skipped any model with *any* icon (including the generic `data:` SVG that OWUI assigns by default), so provider icons were never applied; fixed to skip only user-set custom URLs + - *Race condition* — `_sync_model_icons()` was called before `pipes()` returned, i.e. before OWUI registered the models; OWUI then overwrote the early insert with its own default icon; fixed by also calling `_sync_model_icons()` on cache-hit paths (until all models are confirmed synced) + - *Exception swallowed retry* — DB errors added the model to `_icons_synced` anyway, permanently preventing retry; removed the erroneous add + - *Insert marked as synced prematurely* — after `insert_new_model` the model was marked synced even though OWUI could overwrite it; the insert path no longer updates `_icons_synced` + - *User params clobbered* — `update_model_by_id` used an empty `ModelParams()`, erasing user-configured temperature/system-prompt/etc.; now preserves `existing.params` +- **Icon sync: `function_id` cached at init** — `type(self).__module__` is evaluated once in `__init__` instead of on every `_sync_model_icons()` call +- **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`) +- **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 +- **`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) +- **Image markdown safety** — image URLs are validated against `http://`/`https://` schemes before being embedded; invalid URLs are silently dropped instead of producing broken markdown - Fixed potential payload mutation when `ENABLE_CACHE_CONTROL` is active (deepcopy prevents side effects) - Fixed potential `IndexError` on stream chunks with empty `choices` array - Fixed stream error handler not caching response body before closing connection @@ -65,6 +64,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `_close_think_tag()` helper eliminates duplicated think-tag closure logic (was 5x repeated) - `_stream_response` now closes the response in a `finally` block even on consumer `break` +## [1.2.0] — 2026-02-17 + +> Documented but never tagged on GitHub. The features below shipped to users via direct paste-install of `openrouter_pipe.py` and were rolled into the `v1.3.0` GitHub release. + +### Added + +- See `[1.3.0]` for the consolidated entry list. Original 1.2.0 scope: connection pooling, model list caching, exponential backoff with jitter, fallback deduplication, citation URL sanitization, base URL validation, "error" model guard, empty messages guard, fallback model attribution, HTTP 502 auth detection. + ## [1.1.1] — 2026-02-17 ### Changed @@ -134,10 +141,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Basic error handling and timeout configuration - Model prefix customization - -[Unreleased]: https://github.com/sena-labs/Open-WebUI-Pipe-OpenRouter/compare/v1.2.0...HEAD -[1.2.0]: https://github.com/sena-labs/Open-WebUI-Pipe-OpenRouter/compare/v1.1.1...v1.2.0 -[1.1.1]: https://github.com/sena-labs/Open-WebUI-Pipe-OpenRouter/compare/v1.1.0...v1.1.1 + +[Unreleased]: https://github.com/sena-labs/Open-WebUI-Pipe-OpenRouter/compare/v1.3.0...HEAD +[1.3.0]: https://github.com/sena-labs/Open-WebUI-Pipe-OpenRouter/compare/v1.1.0...v1.3.0 [1.1.0]: https://github.com/sena-labs/Open-WebUI-Pipe-OpenRouter/compare/v1.0.0...v1.1.0 [1.0.0]: https://github.com/sena-labs/Open-WebUI-Pipe-OpenRouter/compare/v0.1.0...v1.0.0 [0.1.0]: https://github.com/sena-labs/Open-WebUI-Pipe-OpenRouter/releases/tag/v0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 582699b..365955c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,7 +55,7 @@ By participating, you agree to uphold this code. | Command | Description | | --- | --- | -| `python test_pipe.py` | Run the full unit test suite (252 tests) | +| `python test_pipe.py` | Run the full unit test suite (431 tests) | | `python integration_test.py` | Run live API tests (requires `OPENROUTER_API_KEY`) | ## Deliverable-PR playbook @@ -74,7 +74,7 @@ of value. The playbook: incidental refactors. 3. **Add or update tests.** A change without test coverage needs a written justification - in the PR body. The unit test suite must remain at 252/252. + in the PR body. The unit test suite must remain at 431/431. 4. **Validate locally** using the same commands CI runs: diff --git a/README.md b/README.md index cac970f..3f8f542 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Python](https://img.shields.io/badge/Python-%E2%89%A53.10-blue)](https://www.python.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -Access **300+ AI models** through OpenRouter directly inside Open WebUI — with provider routing, +Access **340+ AI models** through OpenRouter directly inside Open WebUI — with provider routing, reasoning tokens, streaming, fallbacks, and cache control out of the box. ## Feature gallery @@ -58,7 +58,7 @@ reasoning tokens, streaming, fallbacks, and cache control out of the box. - **Middle-out compression** — fits long prompts within context windows (`transforms: ["middle-out"]`). - **Cache control** — Anthropic-style `cache_control` injection on the longest message chunk. - **Citations** — `[n]` references from web-search-enabled models are converted to markdown links. -- **Provider icons** — 22 provider logos synced directly into Open WebUI's model database. +- **Provider icons** — 13 provider logos synced directly into Open WebUI's model database. - **Retry logic** — exponential backoff with jitter on timeout and connection errors. - **FREE_ONLY mode** — filter to show only free-tier models (`:free` suffix or `0/0` pricing). - **Pre-flight validation** — invalid API keys are caught at model-fetch time, not after sending a message. @@ -96,7 +96,7 @@ All OpenRouter models will appear in the model selector immediately. git clone https://github.com/sena-labs/Open-WebUI-Pipe-OpenRouter.git cd Open-WebUI-Pipe-OpenRouter pip install -r requirements.txt -python test_pipe.py # 252 tests — verify everything is green +python test_pipe.py # 431 tests — verify everything is green ``` ## Usage @@ -199,8 +199,8 @@ The pipe implements the **Manifold** pattern: one pipe entry point that surfaces Open-WebUI-Pipe-OpenRouter/ ├── openrouter_pipe.py # Main pipe source — install this in Open WebUI ├── function.json # Open WebUI community manifest -├── test_pipe.py # Unit test suite (252 tests) -├── integration_test.py # Live API integration tests (47 tests) +├── test_pipe.py # Unit test suite (431 tests) +├── integration_test.py # Live API integration tests (43 assertions) ├── TESTING.md # Manual pre-release checklist ├── SECURITY.md # Security policy ├── CONTRIBUTING.md # Contribution guidelines @@ -229,7 +229,7 @@ It also removes `user` when sent as a dict (Open WebUI format) since OpenRouter ## Development ```bash -python test_pipe.py # Unit tests (252 tests) +python test_pipe.py # Unit tests (431 tests) python integration_test.py # Live API tests (requires OPENROUTER_API_KEY) ``` diff --git a/SECURITY.md b/SECURITY.md index 369f68c..bcf7f2b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,10 +7,10 @@ critical-only fixes on the previous one. | Version | Status | Security fixes | | ------- | ------------------- | -------------- | -| 1.2.x | :white_check_mark: | active | -| 1.1.x | :white_check_mark: | critical only | -| 1.0.x | :x: | end-of-life | -| < 1.0 | :x: | end-of-life | +| 1.3.x | :white_check_mark: | active | +| 1.2.x | :white_check_mark: | critical only | +| 1.1.x | :x: | end-of-life | +| < 1.1 | :x: | end-of-life | ## Reporting a Vulnerability @@ -70,7 +70,7 @@ The pipe implements the following security practices: Every push to `main` and every pull request runs: -- **Unit tests** (`.github/workflows/tests.yml`) — 252 tests across Python 3.10–3.13. Failures block merge. +- **Unit tests** (`.github/workflows/tests.yml`) — 431 tests across Python 3.10–3.13. Failures block merge. ## Disclosure Policy diff --git a/TESTING.md b/TESTING.md index 88c7a05..4887a39 100644 --- a/TESTING.md +++ b/TESTING.md @@ -194,8 +194,8 @@ Must exit with `All tests passed! ✓` and `✗ Failed: 0`. If any test fails, * ## Quick pre-release checklist -- [ ] `python test_pipe.py` → 252 passed, 0 failed -- [ ] `python integration_test.py` → 47/47 +- [ ] `python test_pipe.py` → 431 passed, 0 failed +- [ ] `python integration_test.py` → 43/43 - [ ] Empty API key → clear error message in model selector - [ ] Valid API key → 340+ models with provider icons - [ ] Non-streaming chat works diff --git a/function.json b/function.json index 92dad8e..c132bb7 100644 --- a/function.json +++ b/function.json @@ -3,13 +3,13 @@ "name": "OpenRouter Pipe", "type": "manifold", "meta": { - "description": "Access 300+ AI models through OpenRouter directly inside Open WebUI. Features provider routing, reasoning tokens with tags, full SSE streaming, model fallbacks, middle-out compression, Anthropic cache control, citations, 22 provider icons, and configurable retry logic.", + "description": "Access 340+ AI models through OpenRouter directly inside Open WebUI. Features provider routing, reasoning tokens with tags, full SSE streaming, model fallbacks, middle-out compression, Anthropic cache control, citations, 13 provider icons, and configurable retry logic.", "manifest": { "title": "OpenRouter Pipe", "author": "Sena Labs", "author_url": "https://github.com/sena-labs", "funding_url": "https://github.com/sponsors/sena-labs", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "required_open_webui_version": "0.4.0", "requirements": ["requests>=2.20", "pydantic>=2.0"] @@ -32,6 +32,6 @@ "content": "openrouter_pipe.py", "is_active": true, "is_global": true, - "updated_at": "2026-02-17T00:00:00.000Z", + "updated_at": "2026-05-07T00:00:00.000Z", "created_at": "2026-01-21T00:00:00.000Z" } \ No newline at end of file diff --git a/integration_test.py b/integration_test.py index f6c7880..8fdd156 100644 --- a/integration_test.py +++ b/integration_test.py @@ -1,5 +1,5 @@ """ -Integration test for OpenRouter Pipe v1.2.0 +Integration test for OpenRouter Pipe v1.3.0 Tests the pipe against the LIVE OpenRouter API. Usage: diff --git a/openrouter_pipe.py b/openrouter_pipe.py index 442b3b7..8d4f3c8 100644 --- a/openrouter_pipe.py +++ b/openrouter_pipe.py @@ -3,12 +3,12 @@ author: Sena Labs author_url: https://github.com/sena-labs funding_url: https://github.com/sponsors/sena-labs -version: 1.2.0 +version: 1.3.0 license: MIT icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImJnIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIxMDAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjNmQyOGQ5Ii8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjYTc4YmZhIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3Qgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIHJ4PSIyMCIgZmlsbD0idXJsKCNiZykiLz48cGF0aCBkPSJNMjAgNTAgQzIwIDMwLCA0MCAzMCwgNTAgMzAgTDUwIDIyIEw2OCA0MCBMNTAgNTggTDUwIDUwIEM0MCA1MCwgMzUgNDUsIDMwIDUwIEMyNSA1NSwgMjAgNzAsIDIwIDUwIFoiIGZpbGw9IndoaXRlIiBvcGFjaXR5PSIwLjk1Ii8+PGNpcmNsZSBjeD0iNzgiIGN5PSIzMCIgcj0iNyIgZmlsbD0id2hpdGUiIG9wYWNpdHk9IjAuOCIvPjxjaXJjbGUgY3g9IjgyIiBjeT0iNTAiIHI9IjciIGZpbGw9IndoaXRlIiBvcGFjaXR5PSIwLjk1Ii8+PGNpcmNsZSBjeD0iNzgiIGN5PSI3MCIgcj0iNyIgZmlsbD0id2hpdGUiIG9wYWNpdHk9IjAuOCIvPjxsaW5lIHgxPSI2OCIgeTE9IjQwIiB4Mj0iNzYiIHkyPSIzMiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIiBvcGFjaXR5PSIwLjUiLz48bGluZSB4MT0iNjgiIHkxPSI0MCIgeDI9Ijc2IiB5Mj0iNTAiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMiIgb3BhY2l0eT0iMC41Ii8+PGxpbmUgeDE9IjY4IiB5MT0iNDAiIHgyPSI3NiIgeTI9IjY4IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIG9wYWNpdHk9IjAuNSIvPjwvc3ZnPg== required_open_webui_version: 0.4.0 requirements: requests>=2.20, pydantic>=2.0 -description: Access 300+ AI models through OpenRouter directly inside Open WebUI. Features provider routing, reasoning tokens with tags, full SSE streaming, model fallbacks, middle-out compression, Anthropic cache control, citations, 22 provider icons, and configurable retry logic. +description: Access 340+ AI models through OpenRouter directly inside Open WebUI. Features provider routing, reasoning tokens with tags, full SSE streaming, model fallbacks, middle-out compression, Anthropic cache control, citations, 13 provider icons, and configurable retry logic. """ import copy diff --git a/test_pipe.py b/test_pipe.py index 09fec7a..bcd2319 100644 --- a/test_pipe.py +++ b/test_pipe.py @@ -1,5 +1,5 @@ """ -Comprehensive test suite for OpenRouter Pipe v1.2.0 +Comprehensive test suite for OpenRouter Pipe v1.3.0 Runs with: python test_pipe.py Author: Sena Labs (https://github.com/sena-labs)