Skip to content

feat: add transform_api_request plugin hook for full-payload request transformation - #43241

Closed
raymondclowe wants to merge 1 commit into
NousResearch:mainfrom
raymondclowe:feat/transform-api-request-hook
Closed

feat: add transform_api_request plugin hook for full-payload request transformation#43241
raymondclowe wants to merge 1 commit into
NousResearch:mainfrom
raymondclowe:feat/transform-api-request-hook

Conversation

@raymondclowe

Copy link
Copy Markdown

Summary

Adds a transform_api_request plugin hook that fires once per API call, receiving the mutable api_kwargs dict by reference — the same dict passed to the provider client on the next line.

Unlike pre_api_request (observational, shallow-copied messages), this hook is mutative: plugins can modify messages, model, and other parameters before they reach the provider. Mutations are ephemeral — they affect only the current API call, never the persisted session DB.

Motivation

Closes #43237. See that issue for detailed rationale, use cases, and comparison to the related transform_api_message hook (#20307).

Use cases enabled:

  • Context compression (Headroom, etc.) — compress full conversation context before the API call
  • Security scanning — cross-message secret detection
  • Provider format translation — rewrite requests between provider formats
  • Cost-aware routing — inspect payload to choose cheap vs. expensive models

Changes

File Change
hermes_cli/plugins.py Add transform_api_request to VALID_HOOKS
agent/conversation_loop.py Invoke hook between pre_api_request and the actual client API call, passing api_kwargs by mutable reference
hermes_cli/hooks.py Add test-fixture kwargs
tests/run_agent/test_run_agent.py Test that the hook receives mutable api_kwargs and can modify messages

Test Plan

  • New test: test_transform_api_request_hook_mutates_api_kwargs — verifies hook receives api_kwargs, can mutate messages, metadata fields present
  • Existing TestRunConversation suite — all 122 tests pass
  • Existing TestPluginHooks suite — all tests pass
  • No regressions in existing pre_api_request / post_api_request hook tests

Scope

~86 lines across 4 files. The integration point already exists — this adds a hook invocation in the gap between pre_api_request and the streaming/non-streaming API call.

…transformation

Adds a new plugin hook that fires once per API call, receiving the
mutable api_kwargs dict by reference. Unlike pre_api_request
(observational), this hook allows plugins to modify messages, model,
and parameters before they reach the provider.

Changes:
- hermes_cli/plugins.py: add transform_api_request to VALID_HOOKS
- agent/conversation_loop.py: invoke hook between pre_api_request
  and the actual client API call
- hermes_cli/hooks.py: add test-fixture kwargs

Use cases: context compression (Headroom), security scanning,
provider format translation, cost-aware model routing.

Closes NousResearch#43237
Copilot AI review requested due to automatic review settings June 10, 2026 02:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a new mutative plugin hook (transform_api_request) to allow plugins to modify the outgoing provider request payload (api_kwargs) immediately before dispatch.

Changes:

  • Registers transform_api_request as a supported plugin hook.
  • Invokes transform_api_request in run_conversation with a mutable api_kwargs reference and request metadata.
  • Adds a regression test covering hook invocation and payload shape.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
tests/run_agent/test_run_agent.py Adds a test intended to validate that plugins can mutate api_kwargs before the provider call.
hermes_cli/plugins.py Registers transform_api_request as a recognized hook name.
hermes_cli/hooks.py Documents/example-payload update for the new hook in the hooks listing output.
agent/conversation_loop.py Calls the new mutative hook immediately before the API request is sent.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +969 to +982
if has_hook("transform_api_request"):
_invoke_hook(
"transform_api_request",
api_kwargs=api_kwargs,
session_id=agent.session_id or "",
task_id=effective_task_id,
turn_id=turn_id,
model=agent.model,
provider=agent.provider,
base_url=agent.base_url,
api_mode=agent.api_mode,
api_call_count=api_call_count,
api_request_id=api_request_id,
)
Comment thread hermes_cli/hooks.py
Comment on lines +178 to +188
"transform_api_request": {
"session_id": "test-session",
"task_id": "test-task",
"model": "claude-sonnet-4-6",
"provider": "anthropic",
"base_url": "https://api.anthropic.com",
"api_mode": "anthropic_messages",
"api_call_count": 1,
"turn_id": "test-turn",
"request": {"body": {"messages": [{"role": "user", "content": "hello"}]}},
},
Comment on lines +4556 to +4577
def test_transform_api_request_hook_mutates_api_kwargs(self, agent):
"""The transform_api_request hook receives mutable api_kwargs and
plugins can modify messages before they reach the provider."""
self._setup_agent(agent)
resp = _mock_response(content="Compressed response", finish_reason="stop")
agent.client.chat.completions.create.return_value = resp

hook_received_kwargs = {}
transform_called = False

def _transform_hook(name, **kwargs):
nonlocal transform_called, hook_received_kwargs
if name == "transform_api_request":
hook_received_kwargs = dict(kwargs)
# Simulate a compression plugin: strip tool outputs
api_kw = kwargs.get("api_kwargs", {})
msgs = api_kw.get("messages", [])
for m in msgs:
if m.get("role") == "tool" and m.get("content"):
m["content"] = "[compressed]"
transform_called = True
return []
Comment thread hermes_cli/plugins.py
Comment on lines 138 to 142
"post_llm_call",
"pre_api_request",
"post_api_request",
"transform_api_request",
"api_request_error",
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/plugins Plugin system and bundled plugins labels Jun 10, 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

Looks Good:

  • Adds a new mutative plugin hook called transform_api_request that fires once per API call, receives the mutable api_kwargs dict by reference, and lets plugins modify messages, model, and other parameters before the request is sent. This is a clean extension point for context compression, security scanning, format translation, and cost-aware model routing.
  • Correctly documented as mutative (unlike pre_api_request which is observational and shallow-copied).
  • Test verifies the hook receives api_kwargs, can mutate messages, and the mutation flows to the provider call.
  • Hook registered in plugins.py, documented in hooks.py example payload.
  • Only 4 files, +86/-0. Clean feature addition.

Reviewed by Hermes Agent

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused plugin-extension proposal. Automated hermes-sweeper review found that the requested behavior is already available on current main through the existing middleware surface.

  • hermes_cli/middleware.py:77 implements llm_request middleware, which accepts the complete provider kwargs and replaces the effective outgoing request from a plugin return value.
  • agent/conversation_loop.py:1184 applies that middleware to api_kwargs before request observation and provider execution.
  • docs/middleware/README.md:43 documents ctx.register_middleware("llm_request", ...), the full-request payload, and the {"request": ...} replacement contract.
  • This was introduced by 2e0c9083db8425d6e087ba0af6024406aa513d05 (feat(middleware): add adaptive execution intercepts).

A plugin needing the proposed Headroom-style behavior can register llm_request middleware and return its transformed full request payload, so this additional mutative hook would duplicate an existing capability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:implemented-on-main Sweeper: behavior already present on current main type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: transform_api_request hook — plugin-accessible request middleware for full-payload transformation

5 participants