diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index b8229bb5e..0ad4de220 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -35,6 +35,14 @@ _OR_HEADERS = { "X-OpenRouter-Categories": "cli-agent", } +# Nous Portal extra_body for product attribution. +# Callers should pass this as extra_body in chat.completions.create() +# when the auxiliary client is backed by Nous Portal. +NOUS_EXTRA_BODY = {"tags": ["product=hermes-agent"]} + +# Set at resolve time — True if the auxiliary client points to Nous Portal +auxiliary_is_nous: bool = False + # Default auxiliary models per provider _OPENROUTER_MODEL = "google/gemini-3-flash-preview" _NOUS_MODEL = "gemini-3-flash" @@ -91,6 +99,8 @@ def get_text_auxiliary_client() -> Tuple[Optional[OpenAI], Optional[str]]: # 2. Nous Portal nous = _read_nous_auth() if nous: + global auxiliary_is_nous + auxiliary_is_nous = True logger.debug("Auxiliary text client: Nous Portal") return ( OpenAI(api_key=_nous_api_key(nous), base_url=_nous_base_url()), @@ -135,3 +145,12 @@ def get_vision_auxiliary_client() -> Tuple[Optional[OpenAI], Optional[str]]: # 3. Nothing suitable logger.debug("Auxiliary vision client: none available") return None, None + + +def get_auxiliary_extra_body() -> dict: + """Return extra_body kwargs for auxiliary API calls. + + Includes Nous Portal product tags when the auxiliary client is backed + by Nous Portal. Returns empty dict otherwise. + """ + return dict(NOUS_EXTRA_BODY) if auxiliary_is_nous else {} diff --git a/tools/openrouter_client.py b/tools/openrouter_client.py index c960f4f4a..7d30e6eec 100644 --- a/tools/openrouter_client.py +++ b/tools/openrouter_client.py @@ -25,7 +25,15 @@ def get_async_client() -> AsyncOpenAI: api_key = os.getenv("OPENROUTER_API_KEY") if not api_key: raise ValueError("OPENROUTER_API_KEY environment variable not set") - _client = AsyncOpenAI(api_key=api_key, base_url=OPENROUTER_BASE_URL) + _client = AsyncOpenAI( + api_key=api_key, + base_url=OPENROUTER_BASE_URL, + default_headers={ + "HTTP-Referer": "https://github.com/NousResearch/hermes-agent", + "X-OpenRouter-Title": "Hermes Agent", + "X-OpenRouter-Categories": "cli-agent", + }, + ) return _client diff --git a/tools/session_search_tool.py b/tools/session_search_tool.py index 179d485c0..299286d98 100644 --- a/tools/session_search_tool.py +++ b/tools/session_search_tool.py @@ -170,12 +170,15 @@ async def _summarize_session( max_retries = 3 for attempt in range(max_retries): try: + from agent.auxiliary_client import get_auxiliary_extra_body + _extra = get_auxiliary_extra_body() response = await _async_aux_client.chat.completions.create( model=_SUMMARIZER_MODEL, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ], + **({} if not _extra else {"extra_body": _extra}), temperature=0.1, max_tokens=MAX_SUMMARY_TOKENS, ) diff --git a/tools/vision_tools.py b/tools/vision_tools.py index 4b88724ab..456f85583 100644 --- a/tools/vision_tools.py +++ b/tools/vision_tools.py @@ -314,11 +314,14 @@ async def vision_analyze_tool( logger.info("Processing image with %s...", model) # Call the vision API + from agent.auxiliary_client import get_auxiliary_extra_body + _extra = get_auxiliary_extra_body() response = await _aux_async_client.chat.completions.create( model=model, messages=messages, temperature=0.1, max_tokens=2000, + **({} if not _extra else {"extra_body": _extra}), ) # Extract the analysis diff --git a/tools/web_tools.py b/tools/web_tools.py index 8610e38c6..a7f64166e 100644 --- a/tools/web_tools.py +++ b/tools/web_tools.py @@ -242,6 +242,8 @@ Create a markdown summary that captures all key information in a well-organized, if _aux_async_client is None: logger.warning("No auxiliary model available for web content processing") return None + from agent.auxiliary_client import get_auxiliary_extra_body + _extra = get_auxiliary_extra_body() response = await _aux_async_client.chat.completions.create( model=model, messages=[ @@ -250,6 +252,7 @@ Create a markdown summary that captures all key information in a well-organized, ], temperature=0.1, max_tokens=max_tokens, + **({} if not _extra else {"extra_body": _extra}), ) return response.choices[0].message.content.strip() except Exception as api_error: @@ -362,6 +365,8 @@ Create a single, unified markdown summary.""" fallback = fallback[:max_output_size] + "\n\n[... truncated ...]" return fallback + from agent.auxiliary_client import get_auxiliary_extra_body + _extra = get_auxiliary_extra_body() response = await _aux_async_client.chat.completions.create( model=model, messages=[ @@ -370,6 +375,7 @@ Create a single, unified markdown summary.""" ], temperature=0.1, max_tokens=4000, + **({} if not _extra else {"extra_body": _extra}), ) final_summary = response.choices[0].message.content.strip()