fix(auth): add Codex OAuth accounts as distinct pool entries
hermes auth add openai-codex now creates an independent manual:device_code pool entry per account instead of routing through the singleton _save_codex_tokens save path, which collapsed every added account into the latest login (the second add overwrote the first account's singleton-mirrored device_code entry). This is the add-path half of #39236; PR #39243 (already on this branch) fixes the re-auth half. manual:device_code entries refresh from their own token pair (_sync_codex_entry_from_auth_store only adopts the singleton for source=="device_code"), so they need no providers.openai-codex shadow. Adding the first credential marks openai-codex active (the singleton path did this implicitly) so the setup wizard's get_active_provider() check still passes; subsequent adds leave the active provider untouched. Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two distinct accounts keep distinct token pairs. Updates two existing add tests to the pool-only behavior. Co-authored-by: glesperance <info@glesperance.com>
This commit is contained in:
committed by
Teknium
co-authored by
glesperance
parent
761b744abb
commit
c78b3e1d3c
@@ -1182,6 +1182,24 @@ def _store_provider_state(
|
||||
auth_store["active_provider"] = provider_id
|
||||
|
||||
|
||||
def mark_provider_active_if_unset(provider_id: str) -> None:
|
||||
"""Set ``active_provider`` to *provider_id* only when none is set yet.
|
||||
|
||||
Used by ``hermes auth add`` OAuth paths that create credential-pool
|
||||
entries directly (no singleton ``providers.<id>`` block). Adding the
|
||||
very first credential for a provider should make it the active provider
|
||||
so the setup wizard's ``_model_section_has_credentials()`` check (which
|
||||
consults ``get_active_provider()``) does not report "No inference
|
||||
provider configured". Subsequent adds for an already-active setup leave
|
||||
the user's chosen active provider untouched.
|
||||
"""
|
||||
with _auth_store_lock():
|
||||
auth_store = _load_auth_store()
|
||||
if not (auth_store.get("active_provider") or "").strip():
|
||||
auth_store["active_provider"] = provider_id
|
||||
_save_auth_store(auth_store)
|
||||
|
||||
|
||||
def is_known_auth_provider(provider_id: str) -> bool:
|
||||
normalized = (provider_id or "").strip().lower()
|
||||
return normalized in PROVIDER_REGISTRY or normalized in SERVICE_PROVIDER_NAMES
|
||||
|
||||
@@ -13,6 +13,7 @@ from agent.credential_pool import (
|
||||
AUTH_TYPE_OAUTH,
|
||||
CUSTOM_POOL_PREFIX,
|
||||
SOURCE_MANUAL,
|
||||
SOURCE_MANUAL_DEVICE_CODE,
|
||||
STATUS_EXHAUSTED,
|
||||
STRATEGY_FILL_FIRST,
|
||||
STRATEGY_ROUND_ROBIN,
|
||||
@@ -312,15 +313,35 @@ def auth_add_command(args) -> None:
|
||||
creds["tokens"]["access_token"],
|
||||
_oauth_default_label(provider, len(pool.entries()) + 1),
|
||||
)
|
||||
auth_mod._save_codex_tokens(
|
||||
creds["tokens"],
|
||||
last_refresh=creds.get("last_refresh"),
|
||||
# Add a distinct, self-contained pool entry per account (matching the
|
||||
# xai-oauth / google-gemini-cli / qwen-oauth patterns) instead of
|
||||
# routing through the singleton ``_save_codex_tokens`` save path.
|
||||
# The singleton round-trip collapsed every added account into the
|
||||
# latest login: a second ``hermes auth add openai-codex`` overwrote
|
||||
# the first account's singleton-mirrored ``device_code`` entry rather
|
||||
# than creating an independent one (#39236). ``manual:device_code``
|
||||
# entries refresh from their own token pair, so they need no singleton
|
||||
# shadow.
|
||||
entry = PooledCredential(
|
||||
provider=provider,
|
||||
id=uuid.uuid4().hex[:6],
|
||||
label=label,
|
||||
auth_type=AUTH_TYPE_OAUTH,
|
||||
priority=0,
|
||||
source=SOURCE_MANUAL_DEVICE_CODE,
|
||||
access_token=creds["tokens"]["access_token"],
|
||||
refresh_token=creds["tokens"].get("refresh_token"),
|
||||
base_url=creds.get("base_url"),
|
||||
last_refresh=creds.get("last_refresh"),
|
||||
)
|
||||
pool = load_pool(provider)
|
||||
entry = next((item for item in pool.entries() if item.source == "device_code"), None)
|
||||
shown_label = entry.label if entry is not None else label
|
||||
print(f'Saved {provider} OAuth device-code credentials: "{shown_label}"')
|
||||
first_credential = not pool.entries()
|
||||
pool.add_entry(entry)
|
||||
# Adding the first Codex credential should make it the active provider
|
||||
# (the old singleton save path did this implicitly via
|
||||
# _save_provider_state). Subsequent adds leave the active provider as-is.
|
||||
if first_credential:
|
||||
auth_mod.mark_provider_active_if_unset(provider)
|
||||
print(f'Added {provider} OAuth credential #{len(pool.entries())}: "{entry.label}"')
|
||||
return
|
||||
|
||||
if provider == "xai-oauth":
|
||||
|
||||
Reference in New Issue
Block a user