fix(title): publish conversation context for aux title calls

Title generation via the aux client ran without the Agent's ambient
conversation context, so relay targets that key on it (OpenCode Go)
rejected the completion call with HTTP 400.

Publish the webui session id as the Agent conversation context around
the aux completion (restoring it in a finally block) and pass it from
all three title entry paths: background update, background refresh and
the on-demand regenerate helper.

Refs #7470
This commit is contained in:
webtecnica committed 2026-09-08 22:06:32 -03:00
1 parent 7d56234ea4
commit 61f3cf7ed9
3 files changed
+259 -14

No files matched your search

+35 -13
View File
@@ -4560,7 +4560,7 @@ def _generate_llm_session_title_for_agent(agent, user_text: str, assistant_text:
return None, 'llm_invalid', str(raw)[:120]
def _generate_llm_session_title_via_aux(user_text: str, assistant_text: str, agent=None, *, use_agent_model: bool = False) -> tuple[Optional[str], str, str]:
def _generate_llm_session_title_via_aux(user_text: str, assistant_text: str, agent=None, *, use_agent_model: bool = False, conversation_id: str = '') -> tuple[Optional[str], str, str]:
"""Generate a title via dedicated auxiliary LLM route, then sanitize/validate result.
When use_agent_model is False (default), the auxiliary client resolves
@@ -4568,6 +4568,11 @@ def _generate_llm_session_title_via_aux(user_text: str, assistant_text: str, age
prevents the session's chat model (e.g. a Chinese model) from overriding
the dedicated title model. When True, the agent's attrs are passed through
(legacy fallback behaviour).
conversation_id republishes the webui session id as the Agent's ambient
conversation context for the duration of the aux call, so OpenCode relay
targets receive the same ``x-opencode-session`` sticky key as the session's
main turns (#7470). The context is reset in all exit paths.
"""
if use_agent_model and agent:
provider = getattr(agent, 'provider', '')
@@ -4577,13 +4582,30 @@ def _generate_llm_session_title_via_aux(user_text: str, assistant_text: str, age
provider = ''
model = ''
base_url = ''
raw, status = generate_title_raw_via_aux(
user_text,
assistant_text,
provider=provider,
model=model,
base_url=base_url,
)
ctx_token = None
if conversation_id:
try:
from agent.portal_tags import set_conversation_context
ctx_token = set_conversation_context(str(conversation_id))
except Exception:
# Older/absent agent runtime: proceed without conversation context
# (previous behaviour) rather than failing title generation.
ctx_token = None
try:
raw, status = generate_title_raw_via_aux(
user_text,
assistant_text,
provider=provider,
model=model,
base_url=base_url,
)
finally:
if ctx_token is not None:
try:
from agent.portal_tags import reset_conversation_context
reset_conversation_context(ctx_token)
except Exception:
pass
if not raw:
return None, status, ''
title = _sanitize_generated_title(raw)
@@ -4733,9 +4755,9 @@ def _run_background_title_update(session_id: str, user_text: str, assistant_text
if agent and not aux_title_configured:
next_title, llm_status, raw_preview = _generate_llm_session_title_for_agent(agent, user_text, assistant_text)
if not next_title and llm_status in ('llm_error', 'llm_invalid'):
next_title, llm_status, raw_preview = _generate_llm_session_title_via_aux(user_text, assistant_text, agent=agent, use_agent_model=True)
next_title, llm_status, raw_preview = _generate_llm_session_title_via_aux(user_text, assistant_text, agent=agent, use_agent_model=True, conversation_id=session_id)
else:
next_title, llm_status, raw_preview = _generate_llm_session_title_via_aux(user_text, assistant_text)
next_title, llm_status, raw_preview = _generate_llm_session_title_via_aux(user_text, assistant_text, conversation_id=session_id)
if not next_title and agent and llm_status in ('llm_error_aux', 'llm_invalid_aux'):
next_title, llm_status, raw_preview = _generate_llm_session_title_for_agent(agent, user_text, assistant_text)
source = llm_status
@@ -4831,9 +4853,9 @@ def _run_background_title_refresh(session_id: str, user_text: str, assistant_tex
if agent and not aux_title_configured:
next_title, llm_status, raw_preview = _generate_llm_session_title_for_agent(agent, user_text, assistant_text)
if not next_title and llm_status in ('llm_error', 'llm_invalid'):
next_title, llm_status, raw_preview = _generate_llm_session_title_via_aux(user_text, assistant_text, agent=agent, use_agent_model=True)
next_title, llm_status, raw_preview = _generate_llm_session_title_via_aux(user_text, assistant_text, agent=agent, use_agent_model=True, conversation_id=session_id)
else:
next_title, llm_status, raw_preview = _generate_llm_session_title_via_aux(user_text, assistant_text)
next_title, llm_status, raw_preview = _generate_llm_session_title_via_aux(user_text, assistant_text, conversation_id=session_id)
if not next_title and agent and llm_status in ('llm_error_aux', 'llm_invalid_aux'):
next_title, llm_status, raw_preview = _generate_llm_session_title_for_agent(agent, user_text, assistant_text)
if not next_title:
@@ -4896,7 +4918,7 @@ def generate_session_title_for_session(session, *, prefer_latest: bool = False,
with profiles_api.profile_env_for_background_worker(session, "manual title regeneration", logger_override=logger):
if not _aux_title_generation_enabled():
return None, 'title_generation_disabled', ''
next_title, llm_status, raw_preview = _generate_llm_session_title_via_aux(user_text, assistant_text, agent=agent)
next_title, llm_status, raw_preview = _generate_llm_session_title_via_aux(user_text, assistant_text, agent=agent, conversation_id=getattr(session, 'session_id', '') or '')
if next_title:
return next_title, llm_status, raw_preview
fallback_title = _fallback_title_from_exchange(user_text, assistant_text)