feat: zero-config aux + plain non-secret env values for reverse-proxied use#4
Closed
kingsleydon wants to merge 2 commits into
Closed
feat: zero-config aux + plain non-secret env values for reverse-proxied use#4kingsleydon wants to merge 2 commits into
kingsleydon wants to merge 2 commits into
Conversation
…ed use Two narrow additive changes that only affect deployments that have already opted in via PR #2's Direct config path. Single-user CLI flows and OAuth users are byte-identical to before. ## auxiliary_client.py: codex aux direct config `_try_codex()` learned a new entry point — `_try_codex_from_config()` — that mirrors the Direct config path runtime_provider.py added in PR #2 for the main inference flow. When `model.provider == "openai-codex"` AND `model.api_key` is set, build a Codex auxiliary client straight from those values instead of going through the device-code OAuth flow. This unblocks every auxiliary task (vision, web_extract, compression, session_search, skills_hub, approval, mcp, flush_memories) for reverse-proxied deployments where there's no local OAuth state. The existing pool / `_read_codex_access_token` chain is untouched and runs unchanged whenever `api_key` is empty, so single-user CLI workflows see no behavior change. The auxiliary model defaults to `model.default` (the operator's main model) rather than `_CODEX_AUX_MODEL`. In direct-config mode the only models guaranteed to exist are the ones the operator's reverse proxy serves, which is what `model.default` describes; `_CODEX_AUX_MODEL` is hardcoded for ChatGPT-backed Codex compatibility and may not exist on a custom endpoint. Per-task `auxiliary.{task}.model` overrides still win because the caller composes them via `model or default`. Net result: operators only have to set `model.default + model.base_url + model.api_key` once. All eight auxiliary tasks pick the same model automatically — same zero-config UX an OAuth user gets, just sourced from explicit config instead of OAuth tokens. ## web_server.py: plain `value` for non-secret env vars `GET /api/env` now returns a `value` field alongside `redacted_value`. For env vars marked `password: False` in OPTIONAL_ENV_VARS (allow-lists, mode flags, account IDs, etc.), `value` is the plain on-disk string; for `password: True` fields it stays None. Backward compatible: `redacted_value` is unchanged so existing clients keep working. Dashboards that want to round-trip non-secret fields through form inputs can now read `value` instead of forcing the user to retype on every edit. Password-flagged fields still require the rate-limited `/api/env/reveal` endpoint with audit logging — no change there.
…odex branch + log load_config failures Two follow-ups from review of the previous commit: 1. The codex branch in resolve_provider_client() reads OAuth tokens even when callers pass explicit_api_key/explicit_base_url. The raw_codex=True path (run_agent.py:923-925, 5601-5614) and any fallback_providers entry that targets openai-codex would silently fall through to OAuth and fail in reverse-proxied deployments without local Codex auth state. Added a direct-credentials short-circuit at the top of the branch: when explicit_api_key is set, build the OpenAI client straight from it (and explicit_base_url if provided, otherwise _CODEX_AUX_BASE_URL). raw_codex returns the unwrapped client; the standard path returns the CodexAuxiliaryClient-wrapped variant. OAuth flow below remains the fallback when explicit_api_key is empty. 2. The except-Exception in _try_codex_from_config() was silent. Added a logger.warning so config-load failures surface in pod logs instead of being eaten and presenting as "no provider found".
Author
|
Superseded — switching Clawdi to upstream-native |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two narrow additive changes that make Hermes usable end-to-end on a
reverse-proxied deployment (one with a static api_key in config but no
local OAuth state). Both are gated so single-user CLI workflows are
byte-identical to before.
Why
Clawdi runs Hermes inside per-user containers behind a controller that
talks to a sub2api-compatible Codex Responses endpoint via the
model.api_keyDirect config path PR #2 added. Two gaps surfaced:1. Auxiliary tasks all break for direct-config users
_try_codex()only ever reads from the Codex auth store / credentialpool. When the operator pinned an explicit api_key in config.yaml
(the PR #2 path), every auxiliary task — vision, web_extract,
compression, session_search, skills_hub, approval, mcp,
flush_memories — falls into the OAuth chain and returns
None, None, surfacing as the loud startup banner…even though the operator's main-inference api_key would work fine
for these tasks.
2. Non-secret env vars are unreadable
GET /api/envalways redactsredacted_value, even for fieldsmarked
password: FalseinOPTIONAL_ENV_VARS(allow-lists, modeflags, account IDs). Reverse-proxied dashboards can't round-trip
those fields in form inputs without forcing the user to retype on
every edit.
What this PR changes
agent/auxiliary_client.py: codex aux direct-config path_try_codex()learned a new entry point —_try_codex_from_config()— that mirrors the Direct config pathruntime_provider.pyadded for main inference. Whenmodel.provider == \"openai-codex\"ANDmodel.api_keyis set,build a Codex auxiliary client straight from those values instead of
going through the device-code OAuth flow.
The existing pool /
_read_codex_access_tokenchain is untouchedand runs unchanged whenever
api_keyis empty, so single-user CLIworkflows see no behavior change.
The auxiliary model defaults to
model.default(the operator'smain model) rather than
_CODEX_AUX_MODEL. In direct-config modethe only models guaranteed to exist are the ones the operator's
reverse proxy actually serves — which is what
model.defaultdescribes.
_CODEX_AUX_MODELis hardcoded for ChatGPT-backedCodex compatibility and may not exist on a custom endpoint.
Per-task
auxiliary.{task}.modeloverrides still win because thecaller composes them via
model or default.Net result: operators only have to set
model.default + model.base_url + model.api_keyonce. All eightauxiliary tasks pick the same model automatically — same zero-config
UX an OAuth user gets, just sourced from explicit config instead of
OAuth tokens.
hermes_cli/web_server.py: plainvaluefor non-secret env varsGET /api/envnow returns avaluefield alongsideredacted_value. For env vars markedpassword: False,valueis the plain on-disk string; for
password: Truefields it staysNone.Backward compatible —
redacted_valueis unchanged so existingclients keep working. Password-flagged fields still require the
rate-limited
/api/env/revealendpoint with audit logging.Compatibility matrix
model.api_key == \"\")_try_codex_from_configshort-circuits on emptyapi_key, falls through to the unchanged OAuth pathredacted_valuevaluefield is purely additiveTest plan
hermes webwith no env override →_try_codexreaches OAuth path,_CODEX_AUX_MODELreturned (single-user CLI baseline)model.provider=openai-codex, model.api_key=sk-xxx, model.base_url=https://...→_try_codex_from_configreturns(CodexAuxiliaryClient, model.default)model.provider=openai-codex, model.api_key=\"\"→_try_codex_from_configreturns(None, None), OAuth path runs unchangedauxiliary.compression.model=gpt-5.1-codex-mini→ caller'sfinal_modelwins overmodel.defaultGET /api/envforTELEGRAM_BOT_TOKEN(password: True) →valueisNoneGET /api/envforTELEGRAM_ALLOWED_USERS(password: False) →valueis the plain on-disk string