48 lines
1.9 KiB
Python
48 lines
1.9 KiB
Python
"""Fold an agent-as-provider's own activity back into Hermes' turn state.
|
|
|
|
Agent providers (ACP CLI shims, the codex app-server) run their own tools, so that
|
|
work must never come back as pending ``tool_calls`` (Hermes would re-run it) — but
|
|
the self-improvement loop (replays ``messages``) and the skill-review nudge
|
|
(``_iters_since_skill`` counter) go blind if it is merely summarised into
|
|
``reasoning``. The client hands back ``hermes_projected_messages`` (completed
|
|
assistant/tool rows) and ``hermes_provider_tool_iterations`` on the completion
|
|
object; this helper applies them append-only via ``append_message`` (timestamped,
|
|
persisted). Ordinary OpenAI-compatible clients set neither and are unaffected.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from agent.message_metadata import append_message
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
__all__ = ["splice_provider_projection"]
|
|
|
|
|
|
def splice_provider_projection(agent: Any, response: Any, messages: list[dict[str, Any]]) -> int:
|
|
"""Append the provider's projected history rows and tick the nudge counter.
|
|
|
|
Returns the number of rows spliced. Tolerates absent/garbage attributes so a
|
|
third-party OpenAI-compatible client can't break the turn.
|
|
"""
|
|
projected = getattr(response, "hermes_projected_messages", None)
|
|
rows = [m for m in projected if isinstance(m, dict)] if isinstance(projected, list) else []
|
|
for row in rows:
|
|
append_message(messages, row)
|
|
if rows:
|
|
logger.debug(
|
|
"spliced %d provider-projected transcript row(s) from %s", len(rows), getattr(agent, "provider", "?"),
|
|
)
|
|
|
|
try:
|
|
iterations = int(getattr(response, "hermes_provider_tool_iterations", 0) or 0)
|
|
except (TypeError, ValueError):
|
|
iterations = 0
|
|
if iterations > 0:
|
|
agent._iters_since_skill = getattr(agent, "_iters_since_skill", 0) + iterations
|
|
|
|
return len(rows)
|