Merge pull request #31 from living-ip/codex/leo-paid-research-standalone-ack-20260701
Some checks are pending
CI / lint-and-test (push) Waiting to run
Some checks are pending
CI / lint-and-test (push) Waiting to run
Split Telegram paid research acknowledgement from answer
This commit is contained in:
commit
c1eb4f5718
3 changed files with 84 additions and 4 deletions
|
|
@ -65,6 +65,7 @@ from http_chat_proxy import (
|
|||
should_attach_structured_market_context,
|
||||
smart_research_payment_fields_for_message,
|
||||
smart_research_command_names,
|
||||
split_smart_research_auto_resume_reply,
|
||||
)
|
||||
|
||||
# ─── Config ─────────────────────────────────────────────────────────────
|
||||
|
|
@ -859,11 +860,21 @@ async def _poll_smart_research_auto_resume(
|
|||
if classification == "pending":
|
||||
continue
|
||||
if classification == "ready" and proxy_reply:
|
||||
await _reply_text_native(msg, proxy_reply, do_quote=True)
|
||||
ack_reply, answer_reply = split_smart_research_auto_resume_reply(
|
||||
proxy_body,
|
||||
proxy_reply,
|
||||
)
|
||||
if ack_reply:
|
||||
await _reply_text_native(msg, ack_reply, do_quote=True)
|
||||
if answer_reply:
|
||||
await _reply_text_native(msg, answer_reply, do_quote=not ack_reply)
|
||||
visible_reply = "\n\n".join(
|
||||
part for part in (ack_reply, answer_reply) if part
|
||||
) or proxy_reply
|
||||
if msg.from_user:
|
||||
entry = {
|
||||
"user": research_goal[:500],
|
||||
"bot": proxy_reply[:500],
|
||||
"bot": visible_reply[:500],
|
||||
"username": username or "anonymous",
|
||||
}
|
||||
user_key = (msg.chat_id, msg.from_user.id)
|
||||
|
|
@ -879,9 +890,9 @@ async def _poll_smart_research_auto_resume(
|
|||
user_response_times[msg.from_user.id].append(time.time())
|
||||
_record_transcript(
|
||||
msg,
|
||||
proxy_reply,
|
||||
visible_reply,
|
||||
is_bot=True,
|
||||
rio_response=proxy_reply,
|
||||
rio_response=visible_reply,
|
||||
internal={
|
||||
"agent": AGENT_NAME.lower(),
|
||||
"http_research_proxy_auto_resume": True,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,15 @@ from html import escape
|
|||
from typing import Any
|
||||
|
||||
DEFAULT_SMART_RESEARCH_COMMAND_PREFIXES = ("/smart_research", "/paid_research")
|
||||
SMART_RESEARCH_PAYMENT_ACK_TEXT = "Payment received. Research is starting now."
|
||||
_TELEGRAM_COMMAND_NAME_RE = re.compile(r"^[A-Za-z0-9_]+$")
|
||||
_SMART_RESEARCH_PAYMENT_ACK_PREFIX_RE = re.compile(
|
||||
r"^\s*"
|
||||
r"Payment received[.!]\s*"
|
||||
r"(?:Research is starting now[.!]\s*)?"
|
||||
r"(?:I will use the included source-tool budget and continue with the answer here[.!]\s*)?",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
_AUTO_SMART_RESEARCH_RE = re.compile(
|
||||
r"\b("
|
||||
r"research|source|sources|citation|citations|evidence|"
|
||||
|
|
@ -319,6 +327,37 @@ def classify_smart_research_auto_resume_response(
|
|||
return "ignore"
|
||||
|
||||
|
||||
def split_smart_research_auto_resume_reply(
|
||||
payload: dict[str, Any] | None,
|
||||
reply: str | None,
|
||||
) -> tuple[str | None, str | None]:
|
||||
"""Split a paid auto-resume reply into receipt ack and answer text.
|
||||
|
||||
The HTTP route may return one combined reply starting with "Payment
|
||||
received...". Telegram should show that as two messages: a quick payment
|
||||
receipt first, then the research result or clean tool-failure message.
|
||||
"""
|
||||
if not isinstance(reply, str) or not reply.strip():
|
||||
return None, None
|
||||
text = reply.strip()
|
||||
if not isinstance(payload, dict):
|
||||
return None, text
|
||||
|
||||
auto_resume = payload.get("autoResume")
|
||||
paid_work_order = payload.get("paidWorkOrder")
|
||||
paid_resume = (
|
||||
isinstance(auto_resume, dict)
|
||||
and auto_resume.get("activated") is True
|
||||
or isinstance(paid_work_order, dict)
|
||||
and paid_work_order.get("status") == "settled"
|
||||
)
|
||||
if not paid_resume:
|
||||
return None, text
|
||||
|
||||
answer = _SMART_RESEARCH_PAYMENT_ACK_PREFIX_RE.sub("", text, count=1).strip()
|
||||
return SMART_RESEARCH_PAYMENT_ACK_TEXT, answer or None
|
||||
|
||||
|
||||
def build_smart_research_proxy_payload(
|
||||
*,
|
||||
research_goal: str,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from http_chat_proxy import ( # noqa: E402
|
|||
extract_chat_proxy_reply,
|
||||
extract_smart_research_goal,
|
||||
should_attach_structured_market_context,
|
||||
split_smart_research_auto_resume_reply,
|
||||
smart_research_payment_fields_for_message,
|
||||
smart_research_command_names,
|
||||
)
|
||||
|
|
@ -227,6 +228,35 @@ def test_classify_smart_research_auto_resume_response(http_status, payload, expe
|
|||
assert classify_smart_research_auto_resume_response(http_status, payload) == expected
|
||||
|
||||
|
||||
def test_split_smart_research_auto_resume_reply_separates_payment_ack_from_answer():
|
||||
payload = {
|
||||
"autoResume": {"activated": True},
|
||||
"paidWorkOrder": {"status": "settled"},
|
||||
}
|
||||
reply = (
|
||||
"Payment received. Research is starting now. "
|
||||
"I will use the included source-tool budget and continue with the answer here.\n\n"
|
||||
"Based on the Twitter/X results I found, Ranger is being discussed as wound down."
|
||||
)
|
||||
|
||||
ack, answer = split_smart_research_auto_resume_reply(payload, reply)
|
||||
|
||||
assert ack == "Payment received. Research is starting now."
|
||||
assert answer == (
|
||||
"Based on the Twitter/X results I found, Ranger is being discussed as wound down."
|
||||
)
|
||||
|
||||
|
||||
def test_split_smart_research_auto_resume_reply_keeps_non_paid_ready_reply_intact():
|
||||
ack, answer = split_smart_research_auto_resume_reply(
|
||||
{"status": "complete"},
|
||||
"Here is the answer.",
|
||||
)
|
||||
|
||||
assert ack is None
|
||||
assert answer == "Here is the answer."
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("message", "expected"),
|
||||
[
|
||||
|
|
|
|||
Loading…
Reference in a new issue