Skip to content

feat(llm): gate OpenRouter routes on configured key - #11362

Open
undivisible wants to merge 5 commits into
mainfrom
openrouter-fallback
Open

feat(llm): gate OpenRouter routes on configured key#11362
undivisible wants to merge 5 commits into
mainfrom
openrouter-fallback

Conversation

@undivisible

@undivisible undivisible commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Gate the managed-text OpenRouter preference on a nonblank OPENROUTER_API_KEY.
  • Retain direct provider routes without that key and record the shared fallback telemetry.
  • Cover generated/static gateway routes, readiness checks, vendor model IDs, BYOK handling, rate cards, inventory, and regression tests.

Verification

  • make preflight
  • scripts/pre-push origin
  • Focused LLM gateway, QoS, public-chat, and BYOK test suites
  • Pyright: 0 errors
  • Black and git diff --check

Notes

The public-chat and BYOK suites pass independently. Importing them in that order in one pytest process still exposes their existing module-stub contamination, so that ordering was not used as the passing claim.

No live OpenRouter request was made because no API key is configured; key-present behavior is covered with synthetic test fixtures.

Review in cubic


Note

Medium Risk
Touches central model resolution, gateway routing, and BYOK credential mapping across many features; misconfiguration could shift traffic or readiness, though absence of the key preserves prior direct-provider behavior.

Overview
OpenRouter is now opt-in via config. When OPENROUTER_API_KEY is non-blank, a large set of managed text features (including chat_agent and wrapped_analysis) resolve to OpenRouter (gpt-5.6-luna or the existing Gemini model on OR). Without the key, those features keep their direct OpenAI/Anthropic/Gemini routes and emit one-time degraded fallback telemetry.

Gateway and clients follow the same gate. Generated gateway lanes skip YAML overrides that would fight OpenRouter when the key is present; static lanes for public shared chat and chat-structured can switch primary to OpenRouter when configured. Readiness requires OPENROUTER_API_KEY when any active chat route uses OpenRouter. The executor remaps OpenRouter openai/* refs to direct OpenAI for BYOK users with an OpenAI key, applies GPT-5.6 request sanitization on those routes, and adds OpenRouter cost rate cards plus openai/ model ID prefixing in providers.

chat_agent is no longer anthropic-only for get_llm() when OpenRouter is enabled; integration/QoS tests branch accordingly. Docs inventory text reflects key-gated preference instead of OpenRouter-only managed text.

Reviewed by Cursor Bugbot for commit 2c72195. Configure here.

Failure-Class: none

@cursor

cursor Bot commented Aug 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_a6fc4379-67b0-47c2-be62-0ade5bb07312)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0fcd56d83c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +239 to +240
if openrouter_configured():
return (_OPENROUTER_PREFERRED_MODELS.get(feature, _OPENROUTER_PREFERRED_MODEL), 'openrouter')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Prefix OpenRouter's OpenAI model ID

When OPENROUTER_API_KEY is set and gateway feature mode is off, this returns the bare model gpt-5.6-luna with provider openrouter; get_llm() passes that token to providers._api_model_name(), which only prefixes Gemini models. The gateway and new rate card both correctly use openai/gpt-5.6-luna, so direct requests for every newly preferred feature are sent with the wrong OpenRouter model ID and fail instead of using Luna. Canonicalize the OpenAI prefix in the direct-provider path and test the constructed client's model name.

AGENTS.md reference: backend/AGENTS.md:L189-L189

Useful? React with 👍 / 👎.

Comment on lines +214 to +218
if override is not None and not (openrouter_configured() and legacy_provider == 'openrouter'):
model = override.primary.model
provider = override.primary.provider
else:
model = legacy_model

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve vendor BYOK credentials on gateway routes

When both OPENROUTER_API_KEY and gateway feature mode are enabled for a BYOK request, get_llm() forwards the user's vendor key as X-Omi-Byok-OpenAI-Key, but this branch makes the gateway artifact's provider openrouter. executor._execute_route() then checks has_provider_key('openrouter') and fails closed with MISSING_BYOK_KEY before dispatch, even though enrolled BYOK users correctly supplied their required OpenAI key. Remap OpenRouter's vendor-prefixed model to the corresponding vendor provider/model when serving BYOK traffic and add the legacy-principal coverage required for a new fail-closed route.

AGENTS.md reference: AGENTS.md:L121-L121

Useful? React with 👍 / 👎.

Comment on lines +369 to +370
if provider == 'openrouter' and (model.startswith('gpt-') or model.startswith(('o1', 'o3', 'o4'))):
return f'openai/{model}'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize prefixed Luna routes in the gateway executor

With OPENROUTER_API_KEY configured, generated gateway routes use openai/gpt-5.6-luna, but executor._provider_request() recognizes Luna only when the model starts with gpt-5.6, and _sanitize_openai_chat_completions_request() returns unless the provider is literally openai. As a result, the gateway strips Luna cache fields and leaves unsupported non-default temperature or tool reasoning parameters on OpenRouter requests; the desktop gateway preserves caller-supplied temperature, so affected requests can now receive provider 400s instead of the normalization applied to the previous OpenAI route. Normalize by model basename and apply the OpenAI-family guard to OpenRouter's openai/ namespace.

AGENTS.md reference: backend/AGENTS.md:L189-L189

Useful? React with 👍 / 👎.

Comment on lines +305 to +307
'primary': ProviderRef(
provider='openrouter',
model=_provider_model_name('openrouter', model),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the fake-provider synthetic aligned with the route

When OPENROUTER_API_KEY is present, this repoints omi:auto:chat-structured to provider openrouter, but backend/scripts/product_capability_synthetics.py::llm_gateway_fake_provider_check still registers its fake provider only under openai. Running the documented credential-free synthetic in a deployed environment that has the key therefore reaches an unsupported provider and reports FAIL, even though it is intended to exercise the in-process gateway without real provider calls. Build the fake registry from the configured lane's provider refs or override the route config for this check.

Useful? React with 👍 / 👎.

Comment thread backend/utils/llm/model_config.py Outdated
if feature in _openrouter_fallbacks_recorded:
return
_openrouter_fallbacks_recorded.add(feature)
from utils.observability.fallback import record_fallback

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Move the fallback import to module scope

This newly added function-local import violates the backend's explicit requirement that all imports remain at module scope, bypassing the repository's intended import-hierarchy visibility for code shared across backend services. utils.observability.fallback does not introduce a reverse dependency on model_config, so import record_fallback with the other top-level imports instead.

AGENTS.md reference: backend/AGENTS.md:L179-L187

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c1d249375

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +239 to +240
if openrouter_configured():
return (_OPENROUTER_PREFERRED_MODELS.get(feature, _OPENROUTER_PREFERRED_MODEL), 'openrouter')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve legacy Gemini-only BYOK routing

When OPENROUTER_API_KEY is configured, a legacy BYOK principal enrolled with only a Gemini fingerprint can still pass utils.byok._check_byok_validity, but this preference changes Gemini-backed features such as followup to OpenRouter Luna before get_llm() checks request credentials. _effective_byok_provider() consequently looks only for an OpenAI key, finds none, and silently uses Omi's OpenRouter credential instead of the user's supplied Gemini key; before this change the same request constructed the Gemini BYOK client. Resolve the BYOK profile before applying the process-wide preference, or preserve the direct vendor route whenever the request has that provider's enrolled key.

Useful? React with 👍 / 👎.


# Features that can't go through get_llm() (non-ChatOpenAI providers).
_ANTHROPIC_ONLY_FEATURES = {'chat_agent'}
_ANTHROPIC_ONLY_FEATURES = set()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Migrate the live chat-agent tests with the route

When OPENROUTER_API_KEY is present—the documented prerequisite in test_qos_real_llm.py and test_qos_live_cp9.pychat_agent now resolves to OpenRouter Luna and is usable through get_llm(), but the live suites still classify it as Anthropic. test_qos_real_llm.py::test_chat_agent_anthropic, test_qos_live_cp9.py::test_chat_agent, and test_qos_all_features_l1.py::test_premium_chat_agent pass the resulting gpt-5.6-luna model ID to anthropic_client, while the all-features suite also excludes it from its get_llm() cases. The documented real-provider verification therefore fails before exercising the new route; update these classifications for configured-key mode.

AGENTS.md reference: AGENTS.md:L28-L30

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

3 issues found across 10 files

Confidence score: 3/5

  • In backend/utils/llm/model_config.py, adding followup to _OPENROUTER_PREFERRED_FEATURES appears to reroute resolution to OpenRouter (gpt-5.6-luna) whenever OPENROUTER_API_KEY is present, which can override expected BYOK/provider behavior and cause unexpected model behavior or spend changes — tighten feature-to-provider routing so followup only shifts where explicitly intended.
  • In backend/utils/llm/model_config.py, moving chat_agent into _OPENROUTER_PREFERRED_FEATURES while clearing _ANTHROPIC_ONLY_FEATURES changes a previously constrained path, so chat_agent may now run on a different model family with output/quality differences for users — reintroduce an explicit provider guard or capability check for chat_agent.
  • In backend/llm_gateway/gateway/config_loader.py, removing the provider == 'gemini' gate applies the 128-token session_titles budget to OpenRouter routes too, which risks truncated or lower-quality titles on that path — restore provider-specific budgeting or set separate limits per provider.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/utils/llm/model_config.py">

<violation number="1" location="backend/utils/llm/model_config.py:201">
P1: Adding 'followup' to _OPENROUTER_PREFERRED_FEATURES changes its resolved provider/model to ('gpt-5.6-luna', 'openrouter') whenever OPENROUTER_API_KEY is set. Since _effective_byok_provider() maps openrouter+gpt-* models to 'openai', a user who enrolled only a Gemini BYOK key for this Gemini-backed feature will fail the openai-key lookup and silently fall back to Omi's own OpenRouter credential instead of using their Gemini key. Consider resolving the BYOK profile before applying the process-wide OpenRouter preference, or preserve the direct vendor route when the request has that provider's enrolled key.</violation>

<violation number="2" location="backend/utils/llm/model_config.py:206">
P2: 'chat_agent' has been added to _OPENROUTER_PREFERRED_FEATURES and _ANTHROPIC_ONLY_FEATURES has been cleared, so when OPENROUTER_API_KEY is configured, chat_agent now resolves through get_llm() to OpenRouter's gpt-5.6-luna instead of Anthropic. The live QoS test suites that still classify chat_agent as Anthropic-only and route its model ID to anthropic_client will fail under configured-key mode. Update those live-suite classifications to match the new OpenRouter routing for chat_agent.</violation>
</file>

<file name="backend/llm_gateway/gateway/config_loader.py">

<violation number="1" location="backend/llm_gateway/gateway/config_loader.py:318">
P2: The session_titles output budget (max_completion_tokens: 128) was previously applied only when the route resolved to Gemini; dropping the `provider == 'gemini'` condition means it now also caps the OpenRouter path. When OPENROUTER_API_KEY is configured, session_titles resolves to gpt-5.6-luna on OpenRouter (it is in _OPENROUTER_PREFERRED_FEATURES), and gpt-5.6-luna is a reasoning model. With a 128-token max_completion_tokens cap, a reasoning model can consume most of that budget on internal reasoning, likely truncating or degrading generated session titles compared with the prior Gemini-2.5-flash-lite/128 token behavior. Worth confirming the cap is still appropriate for the reasoning route, or setting the cap only for the non-OpenRouter path if title quality regresses.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

'persona_chat_premium',
'smart_glasses',
'session_titles',
'followup',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Adding 'followup' to _OPENROUTER_PREFERRED_FEATURES changes its resolved provider/model to ('gpt-5.6-luna', 'openrouter') whenever OPENROUTER_API_KEY is set. Since _effective_byok_provider() maps openrouter+gpt-* models to 'openai', a user who enrolled only a Gemini BYOK key for this Gemini-backed feature will fail the openai-key lookup and silently fall back to Omi's own OpenRouter credential instead of using their Gemini key. Consider resolving the BYOK profile before applying the process-wide OpenRouter preference, or preserve the direct vendor route when the request has that provider's enrolled key.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/utils/llm/model_config.py, line 201:

<comment>Adding 'followup' to _OPENROUTER_PREFERRED_FEATURES changes its resolved provider/model to ('gpt-5.6-luna', 'openrouter') whenever OPENROUTER_API_KEY is set. Since _effective_byok_provider() maps openrouter+gpt-* models to 'openai', a user who enrolled only a Gemini BYOK key for this Gemini-backed feature will fail the openai-key lookup and silently fall back to Omi's own OpenRouter credential instead of using their Gemini key. Consider resolving the BYOK profile before applying the process-wide OpenRouter preference, or preserve the direct vendor route when the request has that provider's enrolled key.</comment>

<file context>
@@ -164,14 +164,92 @@ class AutoLaneRouteRef:
+        'persona_chat_premium',
+        'smart_glasses',
+        'session_titles',
+        'followup',
+        'onboarding',
+        'app_integration',
</file context>

def _output_budget_for_feature(feature: str) -> dict[str, Any] | None:
"""Keep pilot caps explicit and disabled until an operator enables the experiment."""
if feature == 'session_titles' and provider == 'gemini':
if feature == 'session_titles':

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The session_titles output budget (max_completion_tokens: 128) was previously applied only when the route resolved to Gemini; dropping the provider == 'gemini' condition means it now also caps the OpenRouter path. When OPENROUTER_API_KEY is configured, session_titles resolves to gpt-5.6-luna on OpenRouter (it is in _OPENROUTER_PREFERRED_FEATURES), and gpt-5.6-luna is a reasoning model. With a 128-token max_completion_tokens cap, a reasoning model can consume most of that budget on internal reasoning, likely truncating or degrading generated session titles compared with the prior Gemini-2.5-flash-lite/128 token behavior. Worth confirming the cap is still appropriate for the reasoning route, or setting the cap only for the non-OpenRouter path if title quality regresses.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/llm_gateway/gateway/config_loader.py, line 318:

<comment>The session_titles output budget (max_completion_tokens: 128) was previously applied only when the route resolved to Gemini; dropping the `provider == 'gemini'` condition means it now also caps the OpenRouter path. When OPENROUTER_API_KEY is configured, session_titles resolves to gpt-5.6-luna on OpenRouter (it is in _OPENROUTER_PREFERRED_FEATURES), and gpt-5.6-luna is a reasoning model. With a 128-token max_completion_tokens cap, a reasoning model can consume most of that budget on internal reasoning, likely truncating or degrading generated session titles compared with the prior Gemini-2.5-flash-lite/128 token behavior. Worth confirming the cap is still appropriate for the reasoning route, or setting the cap only for the non-OpenRouter path if title quality regresses.</comment>

<file context>
@@ -280,9 +286,36 @@ def _generated_feature_route_items(
+def _output_budget_for_feature(feature: str) -> dict[str, Any] | None:
     """Keep pilot caps explicit and disabled until an operator enables the experiment."""
-    if feature == 'session_titles' and provider == 'gemini':
+    if feature == 'session_titles':
         return {
             'experiment': 'session_titles',
</file context>

'app_integration',
'trends',
'translation',
'chat_agent',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: 'chat_agent' has been added to _OPENROUTER_PREFERRED_FEATURES and _ANTHROPIC_ONLY_FEATURES has been cleared, so when OPENROUTER_API_KEY is configured, chat_agent now resolves through get_llm() to OpenRouter's gpt-5.6-luna instead of Anthropic. The live QoS test suites that still classify chat_agent as Anthropic-only and route its model ID to anthropic_client will fail under configured-key mode. Update those live-suite classifications to match the new OpenRouter routing for chat_agent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/utils/llm/model_config.py, line 206:

<comment>'chat_agent' has been added to _OPENROUTER_PREFERRED_FEATURES and _ANTHROPIC_ONLY_FEATURES has been cleared, so when OPENROUTER_API_KEY is configured, chat_agent now resolves through get_llm() to OpenRouter's gpt-5.6-luna instead of Anthropic. The live QoS test suites that still classify chat_agent as Anthropic-only and route its model ID to anthropic_client will fail under configured-key mode. Update those live-suite classifications to match the new OpenRouter routing for chat_agent.</comment>

<file context>
@@ -164,14 +164,92 @@ class AutoLaneRouteRef:
+        'app_integration',
+        'trends',
+        'translation',
+        'chat_agent',
+        'wrapped_analysis',
+    }
</file context>

Comment thread backend/tests/unit/test_openrouter_preference_fallback.py
Comment thread backend/tests/unit/test_omi_qos_tiers.py Outdated
@Git-on-my-level

Copy link
Copy Markdown
Collaborator

Thanks for this well-structured PR — the OpenRouter-key gating is clean, the fallback telemetry is properly deduped, and the test coverage (new test_openrouter_preference_fallback.py with key-present/absent/blank/gateway/fallback-telemetry cases) is thorough. All 148 focused unit tests I ran locally pass, and CI is green.

File-by-file observations:

  • backend/utils/llm/model_config.pyopenrouter_configured() correctly treats blank/whitespace keys as absent. _openrouter_preference() cleanly returns the direct route for non-preferred features and records a one-time fallback per feature. The _OPENROUTER_PREFERRED_FEATURES set is large (~37 features) but cohesive. One product decision worth maintainer sign-off: chat_agent was removed from _ANTHROPIC_ONLY_FEATURES (now an empty set) and added to the OpenRouter-preferred set. This means when OPENROUTER_API_KEY is set, chat_agent resolves to gpt-5.6-luna via OpenRouter instead of claude-sonnet-4-6 via Anthropic. That is a significant model/quality/cost trade-off for the primary chat agent — please confirm this is the intended direction.
  • backend/utils/llm/clients.pyANTHROPIC_AGENT_MODEL/COMPLEX_MODEL now read from _active_profile['chat_agent'][0] directly instead of get_model('chat_agent'). This deliberately bypasses the OpenRouter preference layer so the Anthropic client module always gets the direct profile model (claude-sonnet-4-6) regardless of the OpenRouter key. This is consistent with the BYOK/Anthropic proxy path, but it creates a split: get_model('chat_agent') can return OpenRouter while ANTHROPIC_AGENT_MODEL returns the direct model. Worth confirming the call sites that use ANTHROPIC_AGENT_MODEL are the ones that should stay on Anthropic. The BYOK extension for gpt-/o-series models via OpenRouter (_create_byok_client, _effective_byok_provider) is correct.
  • backend/llm_gateway/gateway/config_loader.py_apply_optional_openrouter_routes and the _generated_feature_route_items guard both correctly consult openrouter_configured(). The override-skip logic (not (openrouter_configured() and legacy_provider == 'openrouter')) ensures generated gateway routes follow the same key gate.
  • backend/llm_gateway/routers/health.py — Readiness now checks both OpenRouter and OpenAI chat lanes independently and reports managed_chat_provider accurately. Clean.
  • backend/llm_gateway/config/cost_rate_cards.yaml — Two new OpenRouter rate cards (gpt-5.6-luna, gpt-5-nano) with reasonable micro-USD pricing.
  • backend/docs/llm/model_endpoint_inventory.yaml — Migration status accurately updated to reflect the conditional OpenRouter preference.
  • Tests — test_openrouter_preference_fallback.py, test_omi_qos_tiers.py, test_llm_gateway_route_refs.py, test_llm_gateway_coverage_guardrails.py all updated consistently. The QoS test now monkeypatch.delenv('OPENROUTER_API_KEY') to assert the no-key direct-provider baseline, which is correct.

Leaving for human maintainer review — the chat_agent routing strategy change (Anthropic-only gate removed, OpenRouter-preferred added) is a product direction decision that needs sign-off.


by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.

@Git-on-my-level Git-on-my-level added needs-maintainer-review Needs a human maintainer to sign off before merge backend Backend Task (python) labels Aug 10, 2026
@cursor

cursor Bot commented Aug 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_19628ea0-6888-4e23-bd1d-a44b5c448adf)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e54ac84b28

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread backend/utils/llm/clients.py Outdated
logger.debug('BYOK QoS upgrade: feature=%s %s/%s→%s/%s', feature, model, provider, byok_model, byok_prov)
model, provider = byok_model, byok_prov

if byok_key and gateway_feature_mode and provider not in {'openai', 'gemini', 'openrouter'}:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep OpenAI BYOK traffic on the gateway

When gateway feature mode is enabled and a user supplies an OpenAI BYOK key (for example, conv_discard), the resolved provider is openai, so this exclusion sends the request through _create_byok_client() rather than the gateway. This breaks the existing test_get_llm_feature_gateway_mode_routes_byok_through_gateway_only contract and bypasses gateway routing, accounting, and telemetry for OpenAI-backed BYOK traffic; the new executor remapping already allows an OpenAI key to serve an OpenRouter openai/ route, so this traffic should continue through get_or_create_omi_gateway_llm_for_byok().

Useful? React with 👍 / 👎.

if feature not in _OPENROUTER_PREFERRED_FEATURES:
return direct
if openrouter_configured():
return (_OPENROUTER_PREFERRED_MODELS.get(feature, _OPENROUTER_PREFERRED_MODEL), 'openrouter')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Log the effective OpenRouter routes at startup

When OPENROUTER_API_KEY is nonblank, this makes nearly every preferred feature resolve to OpenRouter, but the startup QoS loop in clients.py still iterates the static _active_profile map and logs the old OpenAI, Gemini, and Anthropic providers. Consequently the logs explicitly intended to make model-cost issues traceable report routes that the process will not use; build that startup output from _get_model_config() (as get_qos_info() already does) or otherwise log the effective preference.

Useful? React with 👍 / 👎.

@cursor

cursor Bot commented Aug 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_e6f04a34-e6db-4c1e-af60-80d9ad678da6)

@Git-on-my-level

Copy link
Copy Markdown
Collaborator

Thanks for the follow-up updates here. I re-reviewed the current head and the implementation now looks technically consistent to me; I do not see a code-level blocker.

Specific observations from the current diff:

  • backend/utils/llm/model_config.py keeps the no-key behavior fail-closed through openrouter_configured() and records the OpenRouter-to-direct fallback once per feature. With a configured key, the preferred feature set intentionally routes broad managed-text traffic, including chat_agent, session_titles, and wrapped_analysis, through OpenRouter.
  • backend/llm_gateway/gateway/config_loader.py now makes generated gateway routes follow the same key gate and applies the explicit OpenRouter overrides for the chat-structured and public shared conversation chat lanes. I also checked the prior session-title concern: the 128-token budget is now an intentional tested contract in test_openrouter_preference_fallback.py, so I am not treating it as a code blocker, but maintainer sign-off on title quality after the model shift is still prudent.
  • backend/llm_gateway/gateway/executor.py correctly normalizes BYOK OpenRouter openai/... refs to the direct OpenAI provider when an OpenAI BYOK key is present, and the request sanitizer handles both direct OpenAI and OpenRouter-prefixed GPT-5.6 models.
  • backend/utils/llm/clients.py now resolves BYOK before choosing direct vs gateway transport, so gateway-mode BYOK no longer gets stranded on the old direct-provider branch. Keeping ANTHROPIC_AGENT_MODEL tied to the direct profile model also preserves the legacy Anthropic client surface while get_llm('chat_agent') can route to OpenRouter when configured.
  • backend/llm_gateway/routers/health.py checks OpenRouter and OpenAI managed chat credentials separately and reports the selected managed chat provider, matching the new route gate.
  • Test coverage is strong: test_openrouter_preference_fallback.py, test_llm_gateway_executor.py, test_llm_provider_plugin_structure.py, test_omi_qos_tiers.py, and the synthetic/gateway guardrail tests cover key-present, key-absent, blank-key, gateway-route, BYOK, vendor-prefix, readiness, and fallback telemetry behavior.

Validation I ran locally with provider/API-key env vars stripped:

  • tests/unit/test_openrouter_preference_fallback.py tests/unit/test_llm_gateway_executor.py tests/unit/test_llm_gateway_route_refs.py tests/unit/test_llm_gateway_coverage_guardrails.py tests/unit/test_product_capability_synthetics.py tests/unit/test_llm_provider_plugin_structure.py — 85 passed.
  • tests/unit/test_omi_qos_tiers.py — 96 passed.

I also tried the broader combined selection including test_llm_provider_plugin_structure.py before test_omi_qos_tiers.py; that exposes existing module-stub pollution from test_llm_provider_plugin_structure.py replacing langchain_openai.ChatOpenAI in-process. Since test_omi_qos_tiers.py passes cleanly in isolation and the focused changed suites pass, I am treating that as a test-isolation issue rather than a production regression in this PR.

Leaving this for human maintainer review because the remaining decision is product/ops direction: switching chat_agent and many managed-text features to prefer OpenRouter Luna when OPENROUTER_API_KEY is configured changes model quality, latency, cost, and provider dependency characteristics.


Automated maintainer review by glm-5.2 on behalf of David.


by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.

@cursor

cursor Bot commented Aug 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_f3e4be1b-67b6-4893-acc2-a7cb9761facd)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 41c7e1988b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +331 to +332
if get_provider('chat_agent') == 'openrouter':
response = get_llm('chat_agent').invoke(SIMPLE_PROMPT)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Compare live assertions against effective routes

With the suite's documented required OPENROUTER_API_KEY, get_model() and get_provider() now return the OpenRouter preference, but test_active_profile_get_model (lines 103-108) and TestP13_QosInfo (lines 407-413) still compare those results with the static _active_profile; both tests therefore fail for the newly preferred features. Fresh evidence beyond the prior chat-agent comment is that these remaining whole-profile assertions were not migrated when this branch was added; derive their expectations from the effective configuration instead.

AGENTS.md reference: AGENTS.md:L28-L30

Useful? React with 👍 / 👎.

- fair_use
gateway_capability_needed: OpenAI-compatible chat-completions lane with provider refs for openai, native Vertex Gemini, openrouter, perplexity, and anthropic.
migration_status: get_llm centrally switches Omi-managed feature traffic to generated gateway lanes with OMI_LLM_GATEWAY_FEATURE_MODE=gateway and does not retain a direct-provider fallback; gateway Gemini uses native Vertex generateContent authenticated by GKE Workload Identity; Gemini BYOK remains unsupported and fails closed.
migration_status: get_llm centrally switches Omi-managed feature traffic to generated gateway lanes with OMI_LLM_GATEWAY_FEATURE_MODE=gateway; managed text prefers OpenRouter Luna only when OPENROUTER_API_KEY is configured and otherwise retains the direct provider route; gateway Gemini uses native Vertex generateContent authenticated by GKE Workload Identity; Gemini BYOK remains unsupported and fails closed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Update the public chat route inventory

When OPENROUTER_API_KEY is configured, _apply_optional_openrouter_routes() also changes omi:auto:public-shared-conversation-chat from OpenAI gpt-5-nano to OpenRouter openai/gpt-5.6-luna, but the surface inventory immediately below this new migration statement still declares current_provider_model: gateway-only openai/gpt-5-nano. This leaves the checked-in provider/model inventory wrong in the configured production mode, including its credential and cost implications; document the conditional OpenRouter route for this dedicated surface.

AGENTS.md reference: AGENTS.md:L33-L33

Useful? React with 👍 / 👎.

Comment on lines +193 to +195
gateway_config = llm_gateway_dependencies.get_gateway_config()
lane = gateway_config.lanes['omi:auto:chat-structured']
route = gateway_config.route_artifacts[lane.active_route]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore service tokens when route discovery fails

If gateway configuration loading or the chat-structured lane/route lookup raises, these new operations fail before entering the try/finally, after both service-token environment variables have already been overwritten with the sentinel. timed_check() catches the exception and continues running the synthetic process, so later checks inherit the fake credentials instead of the caller's original environment; include route discovery and fake-registry setup inside the cleanup scope.

Useful? React with 👍 / 👎.

@cursor

cursor Bot commented Aug 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_2874f0e2-5185-4b88-9fb0-f8c163277694)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2c721950ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +201 to +205
'session_titles',
'followup',
'onboarding',
'app_integration',
'trends',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Update the Gemini live-test expectations

When the documented OPENROUTER_API_KEY is set, these features now resolve to gpt-5.6-luna/openrouter, but test_qos_real_llm.py::TestPremiumGemini still asserts that each resolves to gemini-2.5-flash-lite, so the documented real-provider suite fails before invoking the newly selected route. The related CP9 test also still skips these effective OpenRouter routes when GEMINI_API_KEY is absent even though that key is documented as optional; classify the tests from get_provider() or otherwise derive their expectations from the effective configuration.

AGENTS.md reference: AGENTS.md:L28-L30

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Backend Task (python) needs-maintainer-review Needs a human maintainer to sign off before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants