Commit graph

10 commits

Author SHA1 Message Date
Charles Inglis
f9de93f42e refactor: make nested-route reasoning deny boundary-based, not prefix-based
Structural hardening per user request, following the 3rd round of the
same bypass class on PR #5313's reasoning_efforts feature:

Round 1: plain-ordering regression (provider-config short-circuit ran
before the nested-route deny).
Round 2: a provider-qualified hint (@custom:<slug>:vertex/gemini-...)
left a slug fragment that the deny's prefix-match missed.

Both were legitimate, narrowly-targeted fixes, but the pattern —
_nested_route_reasoning_denied() requiring the model string to START
WITH the route prefix — meant every future wrapper/nesting scheme
would need the strip logic to be updated in lockstep, and a missed
case fails OPEN (reasoning re-enabled on a route that must never show
it), which is the wrong failure mode for a security-adjacent guard.

This commit removes that class of bug instead of patching its latest
instance: _nested_route_reasoning_denied() now searches for the
vertex/gemini- or gemini_cli/gemini- pattern ANYWHERE in the string at
a non-alphanumeric boundary, rather than requiring it at position 0.
Correctness no longer depends on _strip_provider_hint_for_reasoning()
having stripped exactly the right prefix first — any number of opaque
wrapper layers (@provider:, a named custom-provider slug, or any
nesting scheme not yet invented) can precede the route and the deny
still fires.

Verified: all historical bypass strings (round 1 and round 2, plus a
hypothetical deeper double-wrapped case) now correctly deny; embedded
substrings that must NOT match (e.g. 'notvertex/gemini-x') correctly
don't, thanks to the boundary lookbehind.

Added test_nested_route_deny_is_boundary_based_not_prefix_based
locking in the structural invariant directly, independent of any
particular wrapper scheme.

Verification:
./scripts/test.sh tests/test_reasoning_effort_model_capabilities.py tests/test_custom_provider_bare_model_reasoning.py
# 54 passed in 1.31s

Full suite: 11257 passed, 64 pre-existing failures (identical file/test
set as the prior commit's baseline — network/credential/profile-isolation/fd-leak
tests, unrelated to reasoning_efforts).
2026-07-01 10:02:45 -05:00
Charles Inglis
0109baa142 fix: strip named-provider slug before nested-route reasoning deny
Resolve gate certifier feedback on PR #5313 (deeper bypass, round 3):

A provider-qualified hint like `@custom:<slug>:vertex/gemini-image-1.0`
still bypassed `_nested_route_reasoning_denied()`. The old
`_strip_provider_hint_for_reasoning()` did a naive first-colon split,
which only removed the leading `@custom:` wrapper and left the named
provider's slug (e.g. `agg:vertex/gemini-image-1.0`) attached to the
model id. That leftover slug fragment no longer starts with
`vertex/gemini-`, so the nested-route deny missed it and a configured
`providers.<name>.reasoning_efforts` / `custom_providers[].reasoning_efforts`
allowlist re-enabled reasoning controls on Gemini image/embedding routes
that must never expose them.

Fix: `_strip_provider_hint_for_reasoning()` now accepts the resolved
provider id and strips the exact `@{provider}:` prefix first (e.g.
`@custom:agg:`), so both the wrapper and the slug are removed in one
pass before the nested-route deny check runs. Falls back to the
original first-colon split when no provider is supplied, preserving
existing behavior for plain `@provider:model` hints.

Verified in-process: both
`@custom:agg:vertex/gemini-image-1.0` and
`@custom:agg:vertex/gemini-embedding-001` now correctly resolve to []
instead of leaking the configured ["low", "high"].

Added regression test extending the existing nested-route-deny test to
cover provider-qualified hinted models.

Verification:
`./scripts/test.sh tests/test_reasoning_effort_model_capabilities.py tests/test_custom_provider_bare_model_reasoning.py`
-> 53 passed in 1.23s
Full suite: 11258 passed (63 pre-existing failures unrelated to this
change — network/credential/profile-isolation/fd-leak tests; confirmed
identical failure set on unmodified branch).
2026-07-01 09:35:14 -05:00
Charles Inglis
4f4c8fd183 fix: respect custom provider config and nested route denies
Resolve review feedback on PR #5313:
- Read reasoning_efforts from named custom_providers entries for custom:<name>
  providers instead of looking only in providers:<name>.
- Preserve the nested-route deny ordering before the provider-config shortcut
  so Gemini image/embedding routes cannot be re-enabled by config.
- Deduplicate configured effort values before returning them.

Add focused regression tests for bare provider config, named custom provider
config, all-invalid fallthrough, ACP hard guards, and nested route denies.
2026-07-01 07:35:08 -05:00
nesquena-hermes
30cc15975e fix(#4130): coerce stale 'max' in get_reasoning_status read path too (Codex catch)
get_reasoning_status() returned the raw config reasoning_effort, so a stale saved
'max' surfaced on boot/status/chip even though streaming coerces it to xhigh. Now
routes the status value through coerce_reasoning_effort_for_model() with the resolved
model/provider/base_url, so all read paths agree. Added a stale-max->xhigh status
regression test.
2026-06-13 18:50:00 +00:00
Rod Boev
95bb9ebe66 Keep stale max configs on the supported path 2026-06-13 14:33:26 -04:00
Rod Boev
c7c313672c fix(reasoning): hide unsupported max effort (#4119) 2026-06-13 14:33:26 -04:00
nesquena-hermes
81e748b455
Release v0.51.247 — Release HO (stage-q19) (#3521)
## Release v0.51.247 — Release HO (stage-q19)

Backend correctness fix.

### Fixed
| Issue | Author | Fix |
|-------|--------|-----|
| #3505 | @franksong2702 | **Reasoning effort is coerced to a level the active model/provider actually supports** before each request, instead of being sent verbatim and rejected. `openai-codex` `gpt-5` no longer gets `max` (→ `xhigh`); `o1`/`o3`/`o4` clamp to `low`/`medium`/`high`. Coercion only steps *down* (never escalates); `none`/unset preserved. The capability filter is applied across heuristic / models.dev / Copilot / LM Studio paths. |

This is the narrow, correct fix for the detection gap that #3431 tried to address by removing the chip-visibility gate (which we shelved). The chip-visibility gate is **untouched** (Codex confirmed) — `get_reasoning_status`/`_applyReasoningChip` still hide the chip for unconfirmed models.

### Review fix absorbed (Codex + self-flagged)
The first cut **dropped** a configured effort for *unrecognized* models, because capability detection returns `[]` for both "known-unsupported" and "simply-unknown" (custom providers, aggregator-rewritten ids, new releases) — that's a behavior change vs master (which sent it verbatim) and would silently disable reasoning. Fixed: an **empty** capability set now **preserves** the configured effort (provider stays the final authority; worst case = the same rejected request master already produces, i.e. no regression). Known-bad clamps return *non-empty* filtered sets, so they still degrade correctly. Nathan chose this "preserve-for-unknown" behavior. + regression test.

### Gate
- Full pytest suite: **7548 passed, 0 failed**
- ruff: CLEAN · 48 reasoning tests pass (incl. preserve-for-unknown + codex-clamp + never-escalate)
- Codex (regression): SHIP-ONLY-WITH-FIXES (unknown-model drop) → fixed → **SAFE TO SHIP**
- Verified empirically: gpt-5/codex max→xhigh, o3 max/xhigh→high, unknown high→high (preserved), none/unset preserved

Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-03 19:21:26 -07:00
Roberto Villegas
4c4922a0d5
fix(webui): harden reasoning chip provider coverage 2026-05-24 18:42:44 -06:00
Roberto Villegas
d1471fbed7
fix(webui): resolve reasoning efforts for routed codex models 2026-05-24 18:42:44 -06:00
Roberto Villegas
a9ce2889af
fix(ui): hide reasoning chip when model lacks effort levels
Resolve supported reasoning efforts per active model/provider and pass
that context through /api/reasoning so Composer and other non-configurable
models no longer show a misleading effort picker.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 18:42:31 -06:00