Skip to content

feat(openrouter): price typesafe/jev-1.13 and add an openrouter decisions pass-through - #42301

Merged
litellm-stampbot merged 6 commits into
mainfrom
devin/1790022076-openrouter-typesafe-jev
Sep 22, 2026
Merged

litellm-stampbot merged 6 commits into
mainfrom
devin/1790022076-openrouter-typesafe-jev

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • openrouter/typesafe/jev-1.13 had no pricing row, so spend logged as 0
  • OpenRouter rejects this model on /chat/completions, so no proxy route could reach it

How it solves it:

  • Adds the openrouter/typesafe/jev-1.13 row to both cost maps
  • Adds a /openrouter/{path} pass-through rooted at OPENROUTER_API_BASE minus its /v1 (default https://openrouter.ai/api)
  • Prices /alpha/decisions responses from the new row using the shared TypeSafe usage handler; other OpenRouter paths keep generic pass-through logging

User Flow

Before: a developer cannot run a Jev decision through the proxy at all

  1. They add openrouter/typesafe/jev-1.13 to the proxy model list with an OpenRouter key
  2. They send POST https://litellm-domain/v1/chat/completions with "model": "jev" and a messages array
  3. OpenRouter answers 400: "typesafe/jev-1.13 is a decisions model and cannot be used with the chat/completions endpoint. Use the /api/alpha/decisions endpoint instead", with x-litellm-response-cost: 0
  4. They send POST https://litellm-domain/openrouter/alpha/decisions and get 404, since the proxy has no such route

After: the same developer sends a decisions request through the proxy and it is priced

  1. They set OPENROUTER_API_KEY on the proxy (or a model-list deployment with use_in_pass_through: true)
  2. They send POST https://litellm-domain/openrouter/alpha/decisions with {"model": "typesafe/jev-1.13", "state": "...", "questions": {"blue": {"type": "noul", "instructions": "..."}}}
  3. They get 200 with OpenRouter's body: {"model": "typesafe/jev-1.13-20260917", "answers": {...}, "usage": {"input_tokens": 282, "output_tokens": 20, ...}}
  4. The request is logged under openrouter/typesafe/jev-1.13-20260917 with spend 282 * 4.2e-08 = 1.1844e-05, matching OpenRouter's own usage.cost

Relevant issues

Affected release

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. 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
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to 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_KEY set and a model list entry jev -> openrouter/typesafe/jev-1.13, real OpenRouter calls.

Before (662e5b6)

  1. 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."}]}'
  2. 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.
  3. curl -si http://localhost:4123/openrouter/alpha/decisions ... returns 404 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/v1 with the /v1 stripped, which resolves to the same target shown below, and limits pricing to /alpha/decisions)

  1. 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?"}}}'
  2. Output:
HTTP/1.1 200 OK
x-provider-name: TypeSafe
x-litellm-call-id: 94b95553-030a-4992-be57-2332039af5b4
x-litellm-model-api-base: https://openrouter.ai/api/alpha/decisions

{"model":"typesafe/jev-1.13-20260917","answers":{"blue":{"type":"noul","noul":0.99}},"usage":{"input_tokens":282,"output_tokens":20,"cost":0.000011844},"id":"gen-dec-1790025451-5xbBS4UbLxPw0gOzppNb","provider":"TypeSafe"}
  1. 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 reported usage.cost)

  2. Direct TypeSafe pass-through still prices the same way after the shared handler grew a custom_llm_provider parameter (same proxy, TYPESAFE_API_KEY set, 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?"}}}'

HTTP/1.1 200 OK
x-litellm-model-api-base: https://api.typesafe.ai/v1/systemone
{"model":"jev-1.13.0","answers":{"blue":{"type":"noul","noul":0.99}},"usage":{"input_tokens":282,"output_tokens":20}}

Success callback: model=typesafe/jev-1.13.0 custom_llm_provider=typesafe response_cost=1.1844e-05

Mutation check for the tests: changing the route default to https://openrouter.ai fails test_uses_default_target_when_base_is_unset; dropping the request-model fallback in the pricing lookup fails test_openrouter_decisions_response_is_priced_from_request_model_registry_row with cost 0

Type

🆕 New Feature

Caveats (if any)

Medium

  • Any OpenRouter path is forwarded, but only /alpha/decisions responses are priced; other paths log 0 cost
  • The response model is dated (jev-1.13-20260917), so pricing falls back to the request model row; spend is logged under the dated name

Low

  • Route has no streaming support, same as the TypeSafe pass-through
  • /openrouter/ route pricing uses the cost map, not OpenRouter's usage.cost

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

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/decisions for TypeSafe Jev models). The route forwards all HTTP methods to OPENROUTER_API_BASE (default https://openrouter.ai/api after stripping a trailing /v1) with OPENROUTER credentials, and is wired into the gateway allowlist, lazy-loaded passthrough feature, and OpenAPI snapshots.

Registers openrouter/typesafe/jev-1.13 in the model cost maps and extends the shared TypeSafe pass-through logging handler to take a custom_llm_provider argument. 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.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
devin-ai-integration Bot requested a review from a team September 21, 2026 20:22
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

The PR is not yet safe to merge because decisions requests with a trailing slash still bypass spend calculation, and the provider-specific proxy implementation still violates an explicit repository requirement.

Findings

  1. P1 Trailing Slash Skips Pricing ▶
  2. P2 Provider Logic Outside Integration ▶

Summary

Adds an OpenRouter pass-through route for decisions requests and pricing support for openrouter/typesafe/jev-1.13.

  • Registers /openrouter/{endpoint} across proxy routing, gateway allowlisting, lazy loading, and generated API schemas.
  • Reuses the TypeSafe usage handler to price OpenRouter decisions responses, including request-model fallback for dated response model names.
  • Adds cost-map entries and regression coverage for routing, credentials, URL construction, and spend calculation.
  • Changes since the previous review consist of merging the updated base branch; the OpenRouter feature files themselves are unchanged.

Reviews (4) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..."

Comment thread model_prices_and_context_window.json
@codspeed

codspeed Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing devin/1790022076-openrouter-typesafe-jev (eaea4ae) with main (5d3d99e)

Open in CodSpeed

@codecov

codecov Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

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>
@devin-ai-integration devin-ai-integration Bot changed the title feat(openrouter): add typesafe/jev-1.13 pricing feat(openrouter): price typesafe/jev-1.13 and add an openrouter decisions pass-through Sep 21, 2026
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>
@litellm-stampbot

Copy link
Copy Markdown
Contributor

@greptile run

@litellm-stampbot

Copy link
Copy Markdown
Contributor

@BugBot run

Comment thread litellm/proxy/pass_through_endpoints/success_handler.py Outdated

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous 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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d994218. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@litellm-stampbot

Copy link
Copy Markdown
Contributor

@greptileai

@litellm-stampbot

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous 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")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Trailing Slash Skips Pricing

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.

Suggested change
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"
)

Comment on lines +593 to +613
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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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!

@litellm-stampbot

Copy link
Copy Markdown
Contributor

@greptileai

@litellm-stampbot

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

@litellm-stampbot
litellm-stampbot enabled auto-merge (squash) September 22, 2026 02:42
@litellm-stampbot
litellm-stampbot merged commit 1106b16 into main Sep 22, 2026
98 of 100 checks passed
@litellm-stampbot
litellm-stampbot deleted the devin/1790022076-openrouter-typesafe-jev branch September 22, 2026 02:44
devin-ai-integration Bot added a commit that referenced this pull request Sep 22, 2026
…ions pass-through (#42301)

Backport of #42301 to stable/1.102.x.
Cherry-picked from 1106b16 (main).
devin-ai-integration Bot added a commit that referenced this pull request Sep 23, 2026
…ions pass-through (#42301)

Backport of #42301 to stable/1.101.x.
Cherry-picked from 1106b16 (main).

This branch is waiting to be deployed

1 waiting deployment
e2e-changed — eaea4ae4 Waiting Sep 22, 2026 by devin-ai-integration[bot] via oauth #275
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants