Gateway turns reach the agent enriched with channel-bound skill payloads, sender metadata and other model context, so the first user message the titler saw could be thousands of characters of skill text ahead of the actual question, and new sessions were named after the skill instead of the request. The gateway now captures the inbound event text before that enrichment and passes it as a turn-local title_user_message through TurnContext, the turn runner, AIAgent.run_conversation and the conversation prologue. Only the titler reads it: the main model, persisted history and relay metadata still receive the full enriched message. An explicit empty or whitespace override suppresses titling for that turn, None keeps the existing model-facing fallback (attachment-only turns, paste previews), and the override cannot enable model titling for cron or subagent runs. The value is a per-call argument, so a cached agent never reuses a previous turn's override. Rebased onto current main: the gateway call keeps main's pinned channel prompt for internal events, and the integration fixture's context-prompt stub accepts main's internal= keyword. Ritvik Gautam made this change with Doryani AI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
74 lines
3.3 KiB
Python
74 lines
3.3 KiB
Python
"""Title-only input must not change history, eligibility, or fallback behavior."""
|
|
|
|
from copy import deepcopy
|
|
from types import SimpleNamespace
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
from agent.turn_context import _maybe_title_session_at_turn_start
|
|
|
|
|
|
@pytest.mark.parametrize("override, content, expected", [
|
|
(None, "Ordinary request", "Ordinary request"),
|
|
("Actual question", "Injected model context", "Actual question"),
|
|
(" Actual question ", "Injected model context", "Actual question"),
|
|
("", "Injected model context", None),
|
|
(" ", "Injected model context", None),
|
|
(None, [{"type": "text", "text": "Image caption"},
|
|
{"type": "image_url", "image_url": {"url": "data:image/png;base64,AA=="}}], "Image caption"),
|
|
(None, [{"type": "image_url", "image_url": {"url": "data:image/png;base64,AA=="}}], None),
|
|
])
|
|
def test_title_input_override_is_optional_and_history_is_unchanged(monkeypatch, override, content, expected):
|
|
agent = SimpleNamespace(_session_db=Mock(), session_id="test", _session_db_created=True)
|
|
messages = [{"role": "user", "content": content}]
|
|
original = deepcopy(messages)
|
|
title = Mock()
|
|
monkeypatch.setattr("agent.title_generator.maybe_auto_title", title)
|
|
|
|
_maybe_title_session_at_turn_start(agent, messages, override)
|
|
|
|
assert messages == original
|
|
if expected is None:
|
|
title.assert_not_called()
|
|
else:
|
|
assert title.call_args.args[2] == expected
|
|
assert title.call_args.kwargs["conversation_history"] is messages
|
|
|
|
|
|
@pytest.mark.parametrize("override, expected", [(None, "attachment"), ("Original question", "Original question")])
|
|
def test_title_override_preserves_upstream_paste_preview(monkeypatch, override, expected):
|
|
agent = SimpleNamespace(_session_db=Mock(), session_id="test", _session_db_created=True)
|
|
messages = [{"role": "user", "content": "attachment",
|
|
"display_metadata": {"title_preview": "pasted topic"}}]
|
|
original = deepcopy(messages)
|
|
title = Mock()
|
|
monkeypatch.setattr("agent.title_generator.maybe_auto_title", title)
|
|
_maybe_title_session_at_turn_start(agent, messages, override)
|
|
assert title.call_args.args[2] == expected
|
|
assert title.call_args.kwargs["title_preview"] == "pasted topic"
|
|
assert title.call_args.kwargs["conversation_history"] is messages
|
|
assert messages == original
|
|
|
|
|
|
@pytest.mark.parametrize("text, titleable", [("/help", True), ("[CONTEXT COMPACTION] synthetic handoff", False)])
|
|
def test_title_override_preserves_existing_control_message_eligibility(monkeypatch, text, titleable):
|
|
agent = SimpleNamespace(_session_db=Mock(), session_id="test", _session_db_created=True)
|
|
start = Mock()
|
|
monkeypatch.setattr("agent.memory_provider.spawn_context_thread", start)
|
|
|
|
_maybe_title_session_at_turn_start(agent, [{"role": "user", "content": "injected context"}], text)
|
|
|
|
assert start.called is titleable
|
|
|
|
|
|
@pytest.mark.parametrize("platform", ["cron", "subagent", "CRON"])
|
|
def test_title_override_cannot_enable_titling_for_machine_runs(monkeypatch, platform):
|
|
agent = SimpleNamespace(_session_db=Mock(), session_id="test", platform=platform)
|
|
title = Mock()
|
|
monkeypatch.setattr("agent.title_generator.maybe_auto_title", title)
|
|
|
|
_maybe_title_session_at_turn_start(agent, [], "Actual question")
|
|
|
|
title.assert_not_called()
|