Skip to content

feat(custom-provider): forward session_id as x-session-id HTTP header - #52413

Open
HyeonjeCho wants to merge 2 commits into
NousResearch:mainfrom
HyeonjeCho:feat/custom-provider-session-id-header
Open

feat(custom-provider): forward session_id as x-session-id HTTP header#52413
HyeonjeCho wants to merge 2 commits into
NousResearch:mainfrom
HyeonjeCho:feat/custom-provider-session-id-header

Conversation

@HyeonjeCho

@HyeonjeCho HyeonjeCho commented Jun 25, 2026

Copy link
Copy Markdown

What does this PR do?

The custom provider profile (used for self-hosted OpenAI-compatible backends like SGLang, vLLM, Ollama, llamacpp) did not accept session_id in build_api_kwargs_extras, so no session header was ever sent on any request through the custom provider.

This PR forwards session_id as an x-session-id HTTP header via extra_headers, enabling self-hosted backends to correlate requests by conversation in traffic logs.

Related Issue

Fixes #

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  1. Forward session_id as x-session-id header (29a5bc7)

• plugins/model-providers/custom/init.py: build_api_kwargs_extras now accepts session_id parameter and forwards it astop_level["extra_headers"] = {"x-session-id": session_id} when present
• tests/plugins/model_providers/test_custom_profile.py: New test file with 12 tests covering session_id header emission, Ollamanum_ctx, reasoning disable, combined output, and full transport integration

  1. Normalize custom:* provider slug for profile lookup (c4f77b7)

• agent/chat_completion_helpers.py: Normalize custom:* slugs (e.g. custom:glm-4.7) to "custom" before get_provider_profile()lookup

Why this is needed: When switching models via /model, Hermes stores the full provider slug (e.g. custom:glm-4.7) inagent.provider. However, get_provider_profile() only registers the bare "custom" key — so the lookup returns None, causing theagent to fall through to the legacy kwargs path which does not inject the x-session-id header. Without this fix, the session_idheader from commit 1 only works at session start (where init_agent resolves provider: "custom"), and breaks after any /modelswitch to a named custom provider.

How to Test

  1. python -m pytest tests/plugins/model_providers/test_custom_profile.py -v --tb=short -o 'addopts=' -n 0
  2. Verify 12 tests pass
  3. Optionally: configure a custom provider pointing to a self-hosted SGLang/vLLM endpoint, send a message, and confirm x-session-id header appears in the backend's traffic logs
  4. Regression test for named custom providers: Switch models via /model to a named custom provider (e.g. custom:glm-4.7), thensend a message — confirm x-session-id header still appears in the backend's traffic logs (this would fail without commitc4f77b7)

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform:

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

tests/plugins/model_providers/test_custom_profile.py::TestCustomSessionIdHeader::test_session_id_emits_header PASSED [  8%]
tests/plugins/model_providers/test_custom_profile.py::TestCustomSessionIdHeader::test_no_session_id_omits_header PASSED [ 16%]
tests/plugins/model_providers/test_custom_profile.py::TestCustomSessionIdHeader::test_empty_session_id_omits_header PASSED [ 25%]
tests/plugins/model_providers/test_custom_profile.py::TestCustomSessionIdHeader::test_none_session_id_omits_header PASSED [ 33%]
tests/plugins/model_providers/test_custom_profile.py::TestCustomOllamaNumCtx::test_num_ctx_emitted PASSED [ 41%]
tests/plugins/model_providers/test_custom_profile.py::TestCustomOllamaNumCtx::test_no_num_ctx_omits_options PASSED [ 50%]
tests/plugins/model_providers/test_custom_profile.py::TestCustomReasoningDisable::test_disabled_emits_think_false PASSED [ 58%]
tests/plugins/model_providers/test_custom_profile.py::TestCustomReasoningDisable::test_effort_none_emits_think_false PASSED [ 66%]
tests/plugins/model_providers/test_custom_profile.py::TestCustomReasoningDisable::test_enabled_emits_no_think PASSED [ 75%]
tests/plugins/model_providers/test_custom_profile.py::TestCustomCombinedOutput::test_all_three_present PASSED [ 83%]
tests/plugins/model_providers/test_custom_profile.py::TestCustomFullKwargsIntegration::test_full_kwargs_include_session_header PASSED [ 91%]
tests/plugins/model_providers/test_custom_profile.py::TestCustomFullKwargsIntegration::test_full_kwargs_omit_header_without_session PASSED [100%]

======================== 12 passed, 1 warning in 1.37s =========================

@HyeonjeCho
HyeonjeCho force-pushed the feat/custom-provider-session-id-header branch from 874dbe4 to 8bfa45c Compare June 25, 2026 08:20
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins labels Jun 25, 2026

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approved

Clean feature adding session_id forwarding as an x-session-id HTTP header for the custom provider profile (used for self-hosted OpenAI-compatible backends like SGLang, vLLM, Ollama, llamacpp). This enables self-hosted backends to correlate requests by conversation in traffic logs.

Looks Good

  • build_api_kwargs_extras now accepts session_id parameter and forwards it via extra_headers
  • Safe for None/empty session_id (no header emitted)
  • 12 test cases covering: session_id header emission, Ollama num_ctx, reasoning disable, combined output, full transport integration
  • Minimal diff to production code (3 files, ~10 lines of actual logic)

Note

  • pr_body.md is a development artifact committed to the repo. Please remove it before merge.

Reviewed by Hermes Agent (cron)

Custom/Ollama provider profile now forwards session_id to self-hosted
OpenAI-compatible backends (SGLang, vLLM, etc.) via the x-session-id
HTTP header, enabling request correlation by conversation in traffic
logs.

Previously build_api_kwargs_extras did not accept session_id at all,
so no session header was sent on any request through the custom provider.

Follows the existing per-profile pattern (OpenRouter sends
x-grok-conv-id for Grok models the same way via extra_headers).
@HyeonjeCho
HyeonjeCho force-pushed the feat/custom-provider-session-id-header branch from 8bfa45c to 29a5bc7 Compare June 25, 2026 09:32
@HyeonjeCho

Copy link
Copy Markdown
Author

Code Review Summary

Verdict: Approved

Clean feature adding session_id forwarding as an x-session-id HTTP header for the custom provider profile (used for self-hosted OpenAI-compatible backends like SGLang, vLLM, Ollama, llamacpp). This enables self-hosted backends to correlate requests by conversation in traffic logs.

Looks Good

  • build_api_kwargs_extras now accepts session_id parameter and forwards it via extra_headers
  • Safe for None/empty session_id (no header emitted)
  • 12 test cases covering: session_id header emission, Ollama num_ctx, reasoning disable, combined output, full transport integration
  • Minimal diff to production code (3 files, ~10 lines of actual logic)

Note

  • pr_body.md is a development artifact committed to the repo. Please remove it before merge.

Reviewed by Hermes Agent (cron)

pr_body.md has already been removed. Thanks!

When a named custom provider (e.g. custom:glm-4.7) is set via /model
switch, agent.provider retains the full slug. get_provider_profile()
only registers 'custom', so the lookup returns None and falls through
to the legacy path — which does not emit the x-session-id header.

Normalize custom:* to custom before the profile lookup so the profile
path (and its session_id header injection) is taken for all named
custom providers.
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused custom-provider correlation feature. The header premise remains valid on current main: agent/transports/chat_completions.py:581-590 forwards session_id into the profile hook, while plugins/model-providers/custom/__init__.py:22-60 does not emit an x-session-id header.

Problems

  • The custom:* lookup hunk is stale for the normal named-custom route. Current hermes_cli/runtime_provider.py:1024-1046 canonicalizes resolved named custom providers to provider="custom"; the PR's tests also do not exercise agent.provider="custom:...".
  • This needs a manual salvage rather than a clean cherry-pick: 67df958d already changed CustomProfile's existing top_level handling and added tests/plugins/model_providers/test_custom_profile.py.

Suggested changes

  • Merge the header into the current profile's existing top_level dict and extend the current test file with profile-level and transport-level session-header assertions.
  • Omit the lookup hunk unless a current-head repro establishes a supported path that retains a custom:* runtime provider.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users area/sessions Session lifecycle, resume, persistence, history labels Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants