Files
64d408d5ba feat(gateways): add Requesty gateway (supersedes #2241) (#2484)
* feat(gateways): add Requesty hybrid gateway

Register Requesty as an OpenAI-compatible gateway with a short curated
catalog plus public /v1/models discovery. REQUESTY_API_KEY is only
resolved for the exact https /v1 base on the global, EU, US and AP hosts.

Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>

* test(gateways): cover Requesty model mapping and credential boundary

Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>

* docs: add Requesty gateway setup

Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>

* test(env): cover loading REQUESTY_API_KEY from provider env files

Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>

* docs(env): keep the Requesty EU base URL copyable

Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>

* test(provider-manager): include Requesty in the preset picker order

The ProviderManager tests navigate the preset list by index, so the new
Requesty preset (between OrcaRouter and Runcrate) must be listed or the
custom-provider tests stop one row short and time out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(providers): bind saved Requesty profile keys to the canonical endpoint

Requesty's dedicated key was registered in the gateway descriptor, the
request-time credential gate and the .env allowlist, but not in the
saved-profile credential lifecycle that LLMTR and API Route use. So:

- a keyed `requesty` profile retargeted to a non-Requesty URL installed
  and persisted its saved key as OPENAI_API_KEY for that URL;
- an ambient REQUESTY_API_KEY overrode a canonical profile's saved key,
  both on profile application and on relaunch;
- tmux teammates did not receive REQUESTY_API_KEY.

Withhold the saved key for retargeted Requesty profiles in profile
application and startup persistence, manage REQUESTY_API_KEY like the
other profile dedicated keys (keyless canonical profiles keep the
ambient key; relaunch prefers the selected profile's key and never
carries it to a noncanonical URL), and forward it to teammates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(providers): keep saved Requesty keys off shell-retargeted launches

A relaunch with a shell OPENAI_BASE_URL that points a saved Requesty
profile at a non-canonical URL still carried the profile's persisted
OPENAI_API_KEY to that URL. Drop persisted generic credentials for that
retarget, as the Merge and Runcrate guards already do; explicit shell
credentials still apply.

Also require the persisted profile to be a Requesty profile before its
generic key is recovered as REQUESTY_API_KEY, so a shell retarget of
another profile to Requesty no longer adopts that profile's key over a
shell REQUESTY_API_KEY.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(providers): drop saved Requesty auth and headers on shell retarget

The Requesty retarget boundary only gated the generic OPENAI_API_KEY
selection. Requesty supports custom auth headers, so a saved profile's
credential can also ride in the OPENAI_AUTH_* trio or
ANTHROPIC_CUSTOM_HEADERS, and a shell OPENAI_BASE_URL retarget still sent
those to the new endpoint.

Apply the existing Runcrate retarget handling to a Requesty retarget away
from its saved endpoint, so every credential channel follows the same
rule. A canonical regional switch and a saved non-canonical profile
relaunched at its own URL keep their saved auth, matching profile apply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(providers): bind saved Requesty credentials to a canonical saved URL

The Requesty retarget boundary only checked the launch URL. A profile
saved for a non-canonical Requesty-route URL and relaunched with a shell
OPENAI_BASE_URL on canonical Requesty was not treated as a retarget, so
its proxy key, REQUESTY_API_KEY, custom-auth trio and custom headers all
reached Requesty.

Profile apply already treats only a canonically saved Requesty profile's
key as a Requesty key. Use the same rule on relaunch: saved credentials
follow only between canonical Requesty URLs. Any other launch drops them,
including the dedicated-key fallback. Explicit shell credentials still
apply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(requesty): require CLAUDE_CODE_USE_OPENAI=1 for raw env setup

Without CLAUDE_CODE_USE_OPENAI=1, startup ignores a Requesty
OPENAI_BASE_URL, OPENAI_MODEL and REQUESTY_API_KEY env and falls back to
the default OpenGateway route. The README table row and the .env.example
block listed only the base URL and model. Name the selector in both, as
the PR description already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(requesty): bind route identity and discovery to the canonical endpoint

Requesty's credential was bound to the canonical https /v1 hosts, but
the sibling sites that apply the same boundary for LLMTR, Merge,
Command Code and API Route did not include Requesty:

- URL route resolution, active-profile and capability resolution, and
  provider validation treated any Requesty host or retargeted profile
  as `requesty`, so validation accepted REQUESTY_API_KEY for a URL whose
  requests withhold it and capability surfaces kept the Requesty route.
- Hybrid discovery forwarded a caller or saved key to a noncanonical
  base URL, and a retargeted profile still triggered startup discovery.
- A profile saved for a noncanonical URL persisted no route identity,
  so the relaunch retarget guard missed it and its saved custom auth
  and headers followed a shell retarget onto canonical Requesty.
- REQUESTY_API_KEY was not protected from settings overrides when the
  host manages the provider.

Register Requesty at each of those sites, matching its siblings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(requesty): restore REQUESTY_API_KEY after discovery tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(requesty): clear a copied Requesty key on provider switch

A saved Requesty profile mirrors its key into OPENAI_API_KEY. The
copied-key detection in applyProviderFlag listed LLMTR and the other
dedicated gateways but not Requesty, so --provider openai or openrouter
kept the Requesty key as the generic credential for the new endpoint.
An independent shell OPENAI_API_KEY is still preserved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(requesty): give a Requesty teammate override its own key

Teammates now inherit REQUESTY_API_KEY, and Requesty prefers it over
OPENAI_API_KEY. applyAgentProviderOverrideToEnv cleared and replaced the
dedicated keys of Merge, OrcaRouter, Runcrate and Command Code, but not
Requesty, so an agentModels Requesty override kept using the parent's
key. Clear it for every override and install the configured key for a
canonical Requesty endpoint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(requesty): prefer a mirrored Requesty key over a generic key pool

The request executor treats an OPENAI_API_KEY equal to a dedicated key
(LLMTR, Command Code, and others) as that provider's key and uses it
before the generic OPENAI_API_KEYS pool. REQUESTY_API_KEY was missing
from that list. A saved Requesty profile and a Requesty teammate
override both mirror their key into OPENAI_API_KEY, so an inherited or
ambient OPENAI_API_KEYS pool was sent to Requesty instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(requesty): scope the copied Requesty key to the Requesty route

ccab79e2 let an OPENAI_API_KEY equal to REQUESTY_API_KEY take
precedence as a copied provider key on every route, so a stale mirror
could be sent to another provider such as xAI instead of its own key.
Count it as copied only when the active route is requesty. The pool
test now sets the mirrored state directly instead of running the
override helper against the shared process env.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(requesty): apply the dedicated proxy and placeholder guards to Requesty

A saved Requesty proxy profile carries the requesty route id, but
buildLaunchEnv's noncanonical-launch list and buildStartupEnvFromProfile's
retained-proxy guard did not include Requesty. Ambient OPENAI_API_KEY,
OPENAI_API_KEYS and REQUESTY_API_KEY could reach the proxy on relaunch,
and a concrete shell selection skipped the guard entirely. LLMTR,
Merge, Command Code and API Route already withhold them.

REQUESTY_API_KEY was also missing from the placeholder-aware credential
checks in validation, route credential resolution and the picker, so a
template value such as SUA_CHAVE counted as usable and beat a real
OPENAI_API_KEY.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(requesty): mirror a saved Requesty key on in-process profile apply

A saved Requesty profile reaches the env through two producers. The
startup env (buildOpenAICompatibleStartupEnv) writes the key as both
OPENAI_API_KEY and REQUESTY_API_KEY and stamps the requesty route id.
The in-process apply (applyProviderProfileToProcessEnv) wrote only
OPENAI_API_KEY. Provider-flag switch-away recognizes a copied Requesty
key only when the two match, so after applying a keyed profile and then
selecting another provider, the Requesty secret stayed in
OPENAI_API_KEY and was sent to the new endpoint. The in-process apply
now writes the same mirror and route id as startup, like the ApiSmart,
Concentrate, API Route and Command Code profiles in that block.

The README, .env.example and website note now list the US and AP
regional hosts beside EU. Those four URLs are the only ones that
receive REQUESTY_API_KEY.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(requesty): keep a saved Requesty key from passing as shell input

A keyed canonical Requesty profile writes its saved key into
REQUESTY_API_KEY, both in the startup env and, since 1ce28be5, on
in-process apply. A keyless canonical Requesty profile adopts
REQUESTY_API_KEY as shell input. Nothing recorded where that value came
from, so selecting a keyless profile after a keyed one, in process or
after a relaunch from the keyed startup file, sent requests on the
keyed profile's account.

Requesty now uses the origin marker that Runcrate already uses for the
same keyless-adoption rule. CLAUDE_CODE_PROVIDER_PROFILE_REQUESTY_KEY_SHA256
is written where the saved key is installed (profile apply, startup
bootstrap, profile update, launch env), kept across env reapply,
forwarded to teammate panes, and cleared with the rest of the profile
env. Keyless adoption and the alignment check refuse a key that carries
the marker. Genuine shell input has no marker and still authenticates a
keyless profile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(requesty): restore a keyed Requesty profile's key on reconcile

A keyed canonical Requesty profile installs its saved key as
REQUESTY_API_KEY, and the request executor prefers that variable on the
Requesty route. Settings env is merged into the process after the
profile applies. applyActiveProviderProfileFromConfig then reapplies
the profile only when isProcessEnvAlignedWithProfile reports drift, and
that check compared ApiSmart, API Route and Command Code dedicated keys
but not REQUESTY_API_KEY. A settings or shell REQUESTY_API_KEY
therefore survived reconcile and was sent instead of the selected
profile's saved key.

The alignment check now compares REQUESTY_API_KEY for a keyed canonical
Requesty profile, so reconcile restores the saved key. A keyless profile
still uses shell or settings input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(requesty): state where REQUESTY_API_KEY is sent

REQUESTY_API_KEY is sent only to the four canonical Requesty URLs. Any
other OPENAI_BASE_URL runs as a custom endpoint and uses OPENAI_API_KEY.
The README row, .env.example and website note now say so. The website
note also gains the CLAUDE_CODE_USE_OPENAI=1 raw-env requirement that
0c467009 added to the README and .env.example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* revert(requesty): return Requesty to gateway scope

Requesty is an OpenAI-compatible gateway, like Hicap and OpenRouter,
not a first-class provider. The commits after e04177c4 added
first-class credential ownership for it: saved-profile key handling
across apply, startup, relaunch and reconcile, provider-switch cleanup,
teammate forwarding and overrides, discovery and route-identity
changes, host-managed env, placeholder registries and a key-origin
marker. Each layer exposed another lifecycle edge for review to find,
and none of it is needed for the gateway.

Restore every code and test file to e04177c4: e3b95870, 41f409bf,
7d3dbb86, 55b02074, 89417454, b81ac70f, d84546e9, 433b680a, ccab79e2,
591f4a08, 9b56ef2e, 1ce28be5, 025da9bb and 3c7caf25. Profile and
lifecycle credential handling stays on the shared gateway baseline that
Hicap and OpenRouter use on main.

The request-time gate from the original integration is kept:
REQUESTY_API_KEY is sent only to the exact https /v1 base on the
global, EU, US and AP hosts. Any other OPENAI_BASE_URL uses
OPENAI_API_KEY.

The doc fixes that still match this code are kept: the
CLAUDE_CODE_USE_OPENAI=1 raw-env note, all four hosts, and the
key-scope sentence in the README, .env.example and website entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(requesty): cover the request-time credential gate end to end

The Requesty key gate lives in resolveRouteCredentialValue and was
tested only at the resolver. Drive the real OpenAI shim with a mocked
fetch and check the Authorization header for each base URL. The four
canonical https /v1 hosts get REQUESTY_API_KEY. http, a custom port and
another host never do, and only an OPENAI_API_KEY the user set follows
them. With the gate disabled, the test fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(requesty): isolate the credential gate test env

The gate test cleared variables by prefix and restored the whole
process env. It missed the provider-selection flags, so an inherited
CLAUDE_CODE_USE_GEMINI=1 with GEMINI_API_KEY routed the request to
Gemini and failed the test. The blanket restore could also overwrite
unrelated variables. Use snapshotEnv with an explicit list of
provider-selection and request-auth variables. Snapshot it after taking
the shared lock, clear the list before each case, and restore only
those keys before releasing the lock, as CONTRIBUTING.md asks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(requesty): run the credential gate test in a fresh process

The in-process gate test depended on a hand-kept list of every variable
the OpenAI shim reads. Each review found another one: provider flags,
then OPENAI_AZURE_STYLE and the GitHub token hydration marker. Run the
requests in a child process instead, the pattern the Merge gateway
tests use. Its env is the parent's with clearManagedProfileEnv applied
(the repo's managed provider key list) and a temporary config dir. Each
case sets its own variables inside the child, so nothing inherited
reaches the request and this process's env is never changed. The test
asserts the Authorization header for every canonical and non-canonical
URL, with and without OPENAI_API_KEY, and fails when the gate is
disabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>
Co-authored-by: Thibault Jaigu <thibault.jaigu@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:26:38 -08:00
..