mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-30 16:01:49 +08:00
Compare commits
1 Commits
fix/plugin
...
fix/oauth-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69f0df0402 |
@@ -20,6 +20,7 @@ from hermes_cli.auth import (
|
|||||||
DEFAULT_AGENT_KEY_MIN_TTL_SECONDS,
|
DEFAULT_AGENT_KEY_MIN_TTL_SECONDS,
|
||||||
KIMI_CODE_BASE_URL,
|
KIMI_CODE_BASE_URL,
|
||||||
PROVIDER_REGISTRY,
|
PROVIDER_REGISTRY,
|
||||||
|
_auth_store_lock,
|
||||||
_codex_access_token_is_expiring,
|
_codex_access_token_is_expiring,
|
||||||
_decode_jwt_claims,
|
_decode_jwt_claims,
|
||||||
_import_codex_cli_tokens,
|
_import_codex_cli_tokens,
|
||||||
@@ -27,6 +28,8 @@ from hermes_cli.auth import (
|
|||||||
_load_provider_state,
|
_load_provider_state,
|
||||||
_resolve_kimi_base_url,
|
_resolve_kimi_base_url,
|
||||||
_resolve_zai_base_url,
|
_resolve_zai_base_url,
|
||||||
|
_save_auth_store,
|
||||||
|
_save_provider_state,
|
||||||
read_credential_pool,
|
read_credential_pool,
|
||||||
write_credential_pool,
|
write_credential_pool,
|
||||||
)
|
)
|
||||||
@@ -479,6 +482,67 @@ class CredentialPool:
|
|||||||
logger.debug("Failed to sync from ~/.codex/auth.json: %s", exc)
|
logger.debug("Failed to sync from ~/.codex/auth.json: %s", exc)
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
def _sync_device_code_entry_to_auth_store(self, entry: PooledCredential) -> None:
|
||||||
|
"""Write refreshed pool entry tokens back to auth.json providers.
|
||||||
|
|
||||||
|
After a pool-level refresh, the pool entry has fresh tokens but
|
||||||
|
auth.json's ``providers.<id>`` still holds the pre-refresh state.
|
||||||
|
On the next ``load_pool()``, ``_seed_from_singletons()`` reads that
|
||||||
|
stale state and can overwrite the fresh pool entry — potentially
|
||||||
|
re-seeding a consumed single-use refresh token.
|
||||||
|
|
||||||
|
Applies to any OAuth provider whose singleton lives in auth.json
|
||||||
|
(currently Nous and OpenAI Codex).
|
||||||
|
"""
|
||||||
|
if entry.source != "device_code":
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
with _auth_store_lock():
|
||||||
|
auth_store = _load_auth_store()
|
||||||
|
if self.provider == "nous":
|
||||||
|
state = _load_provider_state(auth_store, "nous")
|
||||||
|
if state is None:
|
||||||
|
return
|
||||||
|
state["access_token"] = entry.access_token
|
||||||
|
if entry.refresh_token:
|
||||||
|
state["refresh_token"] = entry.refresh_token
|
||||||
|
if entry.expires_at:
|
||||||
|
state["expires_at"] = entry.expires_at
|
||||||
|
if entry.agent_key:
|
||||||
|
state["agent_key"] = entry.agent_key
|
||||||
|
if entry.agent_key_expires_at:
|
||||||
|
state["agent_key_expires_at"] = entry.agent_key_expires_at
|
||||||
|
for extra_key in ("obtained_at", "expires_in", "agent_key_id",
|
||||||
|
"agent_key_expires_in", "agent_key_reused",
|
||||||
|
"agent_key_obtained_at"):
|
||||||
|
val = entry.extra.get(extra_key)
|
||||||
|
if val is not None:
|
||||||
|
state[extra_key] = val
|
||||||
|
if entry.inference_base_url:
|
||||||
|
state["inference_base_url"] = entry.inference_base_url
|
||||||
|
_save_provider_state(auth_store, "nous", state)
|
||||||
|
|
||||||
|
elif self.provider == "openai-codex":
|
||||||
|
state = _load_provider_state(auth_store, "openai-codex")
|
||||||
|
if not isinstance(state, dict):
|
||||||
|
return
|
||||||
|
tokens = state.get("tokens")
|
||||||
|
if not isinstance(tokens, dict):
|
||||||
|
return
|
||||||
|
tokens["access_token"] = entry.access_token
|
||||||
|
if entry.refresh_token:
|
||||||
|
tokens["refresh_token"] = entry.refresh_token
|
||||||
|
if entry.last_refresh:
|
||||||
|
state["last_refresh"] = entry.last_refresh
|
||||||
|
_save_provider_state(auth_store, "openai-codex", state)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
_save_auth_store(auth_store)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug("Failed to sync %s pool entry back to auth store: %s", self.provider, exc)
|
||||||
|
|
||||||
def _refresh_entry(self, entry: PooledCredential, *, force: bool) -> Optional[PooledCredential]:
|
def _refresh_entry(self, entry: PooledCredential, *, force: bool) -> Optional[PooledCredential]:
|
||||||
if entry.auth_type != AUTH_TYPE_OAUTH or not entry.refresh_token:
|
if entry.auth_type != AUTH_TYPE_OAUTH or not entry.refresh_token:
|
||||||
if force:
|
if force:
|
||||||
@@ -612,6 +676,10 @@ class CredentialPool:
|
|||||||
)
|
)
|
||||||
self._replace_entry(entry, updated)
|
self._replace_entry(entry, updated)
|
||||||
self._persist()
|
self._persist()
|
||||||
|
# Sync refreshed tokens back to auth.json providers so that
|
||||||
|
# _seed_from_singletons() on the next load_pool() sees fresh state
|
||||||
|
# instead of re-seeding stale/consumed tokens.
|
||||||
|
self._sync_device_code_entry_to_auth_store(updated)
|
||||||
return updated
|
return updated
|
||||||
|
|
||||||
def _entry_needs_refresh(self, entry: PooledCredential) -> bool:
|
def _entry_needs_refresh(self, entry: PooledCredential) -> bool:
|
||||||
|
|||||||
Reference in New Issue
Block a user