Repository navigation
feat(openrouter): price typesafe/jev-1.13 and add an openrouter decisions pass-through - #42301
Conversation
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
|
|
Merging this PR will not alter performance
Comparing |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@greptile run |
|
@BugBot run |
| fastapi_response: Response, | ||
| user_api_key_dict: Annotated[UserAPIKeyAuth, Depends(user_api_key_auth)], | ||
| ): | ||
| base_target_url: Final = get_secret_str("OPENROUTER_API_BASE") or "https://openrouter.ai/api" |
There was a problem hiding this comment.
OpenRouter base path conflicts with existing default
Medium Severity
The new /openrouter/ pass-through reads OPENROUTER_API_BASE but falls back to https://openrouter.ai/api, while every other OpenRouter integration in LiteLLM defaults to https://openrouter.ai/api/v1. When that env var is already set to the existing standard base, decisions requests are forwarded to /api/v1/alpha/decisions instead of /api/alpha/decisions, so the new route fails for deployments that already use OpenRouter chat.
Reviewed by Cursor Bugbot for commit d994218. Configure here.
There was a problem hiding this comment.
Fixed in 5752c81: default is now https://openrouter.ai/api/v1 like the rest, and a trailing /v1 is stripped to derive the api root
…i base and price only decisions responses Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
bugbot run |
| return custom_llm_provider == "typesafe" | ||
|
|
||
| def is_openrouter_decisions_route(self, url_route: str, custom_llm_provider: str | None) -> bool: | ||
| return custom_llm_provider == "openrouter" and urlparse(url_route).path.endswith("/alpha/decisions") |
There was a problem hiding this comment.
A decisions request with a trailing slash produces a path ending in /alpha/decisions/, which does not pass this exact suffix check. The upstream request can still succeed, but its usage and spend are not recorded. Normalize the trailing slash before matching.
| return custom_llm_provider == "openrouter" and urlparse(url_route).path.endswith("/alpha/decisions") | |
| return custom_llm_provider == "openrouter" and urlparse(url_route).path.rstrip("/").endswith( | |
| "/alpha/decisions" | |
| ) |
| base_target_url: Final = get_secret_str("OPENROUTER_API_BASE") or "https://openrouter.ai/api/v1" | ||
| api_root: Final = base_target_url.removesuffix("/").removesuffix("/v1") | ||
| encoded_endpoint: Final = httpx.URL(endpoint).path | ||
| normalized_endpoint: Final = encoded_endpoint if encoded_endpoint.startswith("/") else f"/{encoded_endpoint}" | ||
| base_url: Final = httpx.URL(api_root) | ||
| updated_url: Final = base_url.copy_with( | ||
| path=HttpPassThroughEndpointHelpers.join_base_and_endpoint_path(base_url, normalized_endpoint), | ||
| ) | ||
| openrouter_api_key: Final = passthrough_endpoint_router.get_credentials( | ||
| custom_llm_provider="openrouter", | ||
| region_name=None, | ||
| ) | ||
| endpoint_func: Final = create_pass_through_route( | ||
| endpoint=endpoint, | ||
| target=str(updated_url), | ||
| custom_headers={ # mutable-ok: pass-through request headers require a mutable mapping | ||
| "Authorization": f"Bearer {openrouter_api_key}", | ||
| "Content-Type": "application/json", | ||
| }, | ||
| custom_llm_provider="openrouter", | ||
| is_streaming_request=False, |
There was a problem hiding this comment.
Provider Logic Outside Integration
This proxy module directly implements OpenRouter-specific URL normalization, credential selection, authorization headers, and routing behavior. That violates the repository directive to keep provider-specific code under llms/. This repository requirement must be satisfied before merging by moving this behavior behind the OpenRouter integration and leaving the proxy layer generic.
Rule Used: What: Avoid writing provider-specific code outside of the llms/ directory. Why: This practice ensures better maintainability and reduces complexity over time. Good: ```python # Handle provider-specific logic within llms/vertex_ai/transformation.py ... (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
…router-typesafe-jev
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit eaea4ae. Configure here.


TLDR
Problem this solves:
openrouter/typesafe/jev-1.13had no pricing row, so spend logged as 0/chat/completions, so no proxy route could reach itHow it solves it:
openrouter/typesafe/jev-1.13row to both cost maps/openrouter/{path}pass-through rooted atOPENROUTER_API_BASEminus its/v1(defaulthttps://openrouter.ai/api)/alpha/decisionsresponses from the new row using the shared TypeSafe usage handler; other OpenRouter paths keep generic pass-through loggingUser Flow
Before: a developer cannot run a Jev decision through the proxy at all
openrouter/typesafe/jev-1.13to the proxy model list with an OpenRouter key"model": "jev"and a messages arrayx-litellm-response-cost: 0After: the same developer sends a decisions request through the proxy and it is priced
OPENROUTER_API_KEYon the proxy (or a model-list deployment withuse_in_pass_through: true){"model": "typesafe/jev-1.13", "state": "...", "questions": {"blue": {"type": "noul", "instructions": "..."}}}{"model": "typesafe/jev-1.13-20260917", "answers": {...}, "usage": {"input_tokens": 282, "output_tokens": 20, ...}}openrouter/typesafe/jev-1.13-20260917with spend 282 * 4.2e-08 = 1.1844e-05, matching OpenRouter's ownusage.costRelevant issues
Affected release
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Setup: proxy on localhost:4123 with
OPENROUTER_API_KEYset and a model list entryjev -> openrouter/typesafe/jev-1.13, real OpenRouter calls.Before (662e5b6)
curl -si http://localhost:4123/v1/chat/completions -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d '{"model":"jev","messages":[{"role":"user","content":"Is the sky blue? Answer yes or no."}]}'HTTP/1.1 400 Bad Request,x-litellm-response-cost: 0, body:typesafe/jev-1.13 is a decisions model and cannot be used with the chat/completions endpoint. Use the /api/alpha/decisions endpoint instead.curl -si http://localhost:4123/openrouter/alpha/decisions ...returns404 Not Found(no such route)After (5ae939f; commits 4408ed1 and d994218 only sync generated OpenAPI types and the gateway allowlist; 5752c81 changes the base default to
https://openrouter.ai/api/v1with the/v1stripped, which resolves to the same target shown below, and limits pricing to/alpha/decisions)curl -si http://localhost:4123/openrouter/alpha/decisions -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d '{"model":"typesafe/jev-1.13","state":"The sky is blue on a clear day.","questions":{"blue":{"type":"noul","instructions":"Is the sky described as blue?"}}}'Success callback for that call:
model=openrouter/typesafe/jev-1.13-20260917 custom_llm_provider=openrouter response_cost=1.1844e-05(282 * 4.2e-08, equal to OpenRouter's reportedusage.cost)Direct TypeSafe pass-through still prices the same way after the shared handler grew a
custom_llm_providerparameter (same proxy,TYPESAFE_API_KEYset, real TypeSafe call):curl -si http://localhost:4123/typesafe/v1/systemone -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d '{"model":"jev-latest","state":"The sky is blue on a clear day.","questions":{"blue":{"type":"noul","instructions":"Is the sky described as blue?"}}}'Success callback:
model=typesafe/jev-1.13.0 custom_llm_provider=typesafe response_cost=1.1844e-05Mutation check for the tests: changing the route default to
https://openrouter.aifailstest_uses_default_target_when_base_is_unset; dropping the request-model fallback in the pricing lookup failstest_openrouter_decisions_response_is_priced_from_request_model_registry_rowwith cost 0Type
🆕 New Feature
Caveats (if any)
Medium
/alpha/decisionsresponses are priced; other paths log 0 costmodelis dated (jev-1.13-20260917), so pricing falls back to the request model row; spend is logged under the dated nameLow
/openrouter/route pricing uses the cost map, not OpenRouter'susage.costFinal Attestation
Link to Devin session: https://app.devin.ai/sessions/6af5bb4bbf474464af3e36f31cd88bb5
Open in Devin Desktop: https://app.devin.ai/desktop/session/6af5bb4bbf474464af3e36f31cd88bb5?variant=devin
Note
Medium Risk
New authenticated proxy surface forwards arbitrary OpenRouter paths upstream; spend logging is specialized only for
/alpha/decisions, so other routes may still log zero cost.Overview
Adds OpenRouter LLM pass-through at
/openrouter/{endpoint}so clients can reach non-chat APIs (notably/alpha/decisionsfor TypeSafe Jev models). The route forwards all HTTP methods toOPENROUTER_API_BASE(defaulthttps://openrouter.ai/apiafter stripping a trailing/v1) withOPENROUTERcredentials, and is wired into the gateway allowlist, lazy-loaded passthrough feature, and OpenAPI snapshots.Registers
openrouter/typesafe/jev-1.13in the model cost maps and extends the shared TypeSafe pass-through logging handler to take acustom_llm_providerargument. Pass-through success logging now applies that handler only for TypeSafe and for OpenRouter paths ending in/alpha/decisions, so decisions responses get token usage and registry-based spend; other OpenRouter pass-through paths keep generic logging (no TypeSafe pricing).Reviewed by Cursor Bugbot for commit eaea4ae. Bugbot is set up for automated code reviews on this repo. Configure here.