Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions cueapi/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2174,6 +2174,21 @@ def _resolve_recipient(client, recipient: str) -> str:
help="Decoupled reply target (defaults to the sender).",
)
@click.option("--metadata", default=None, help="JSON metadata blob")
@click.option(
"--mode",
"mode",
default="auto",
type=click.Choice(["live", "bg", "inbox", "webhook", "auto"]),
show_default=True,
help=(
"Delivery mode hint. live = recipient's attached Live session, bg = "
"spawn a fresh background session, inbox = leave in inbox for pull, "
"webhook = POST to recipient's configured webhook, auto = server "
"picks the best supported mode based on recipient capabilities. "
"Server may downgrade if the requested mode isn't supported — see "
"`Sent via X` in the response."
),
)
@click.option(
"--idempotency-key",
"idempotency_key",
Expand All @@ -2195,6 +2210,7 @@ def message_to(
expects_reply: bool,
reply_to_agent: Optional[str],
metadata: Optional[str],
mode: str,
idempotency_key: Optional[str],
) -> None:
"""Send a message to a recipient by name, slug, or agent ID.
Expand All @@ -2219,6 +2235,12 @@ def message_to(
body["metadata"] = json.loads(metadata)
except json.JSONDecodeError:
raise click.UsageError("--metadata must be valid JSON")
# Default-omit discipline: only send delivery_mode when the user opted
# away from `auto`. Server treats absent == auto, so this avoids payload
# noise on the common path and keeps wire-format identical to pre-Surface-6
# senders. `auto` is also redundant to send.
if mode != "auto":
body["delivery_mode"] = mode

headers: dict = {"X-Cueapi-From-Agent": from_agent}
if idempotency_key:
Expand Down Expand Up @@ -2246,6 +2268,20 @@ def message_to(
if m.get("thread_id"):
echo_info("Thread:", m["thread_id"])
echo_info("Delivery state:", m.get("delivery_state", "?"))
# Surface the server's chosen delivery mode. The response's
# `effective_delivery_mode` is the mode the server actually
# used, which may differ from the requested `mode` if the
# recipient doesn't support it (e.g. requested live, recipient
# has no live session, downgraded to inbox).
effective = m.get("effective_delivery_mode")
if effective:
if mode != "auto" and effective != mode:
echo_info(
"Sent via:",
f"{effective} (requested {mode}, recipient does not support it)",
)
else:
echo_info("Sent via:", effective)
downgraded_header = None
try:
downgraded_header = resp.headers.get("X-CueAPI-Priority-Downgraded")
Expand Down
147 changes: 147 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from typing import Any, Optional

import pytest
from click.testing import CliRunner

from cueapi.cli import main
Expand Down Expand Up @@ -2599,3 +2600,149 @@ def test_top_level_help_lists_message_to():
result = runner.invoke(main, ["--help"])
assert result.exit_code == 0
assert "message-to" in result.output


# --- message-to --mode flag (Surface 6 delivery_mode) ---


def test_message_to_mode_default_auto_omits_field(monkeypatch):
# Default is `auto` and the server treats absent == auto, so we don't
# send the field on the common path — keeps wire-format identical to
# pre-Surface-6 senders and avoids payload noise.
holder: dict = {}
_patch_messages_client(
monkeypatch,
holder,
responses={
("POST", "/messages"): lambda: _FakeResp(
201, {"id": "msg_z", "delivery_state": "queued"}
)
},
)
result = runner.invoke(
main,
["message-to", "agt_x", "--from", "y@z", "--body", "hi"],
)
assert result.exit_code == 0, result.output
body = holder["client"].calls[-1][2]
assert "delivery_mode" not in body


def test_message_to_mode_explicit_auto_still_omits_field(monkeypatch):
# User explicitly typing --mode auto is the same wire-format as omitting
# it. No reason to send the redundant field.
holder: dict = {}
_patch_messages_client(
monkeypatch,
holder,
responses={
("POST", "/messages"): lambda: _FakeResp(
201, {"id": "msg_z", "delivery_state": "queued"}
)
},
)
result = runner.invoke(
main,
["message-to", "agt_x", "--from", "y@z", "--body", "hi", "--mode", "auto"],
)
assert result.exit_code == 0, result.output
body = holder["client"].calls[-1][2]
assert "delivery_mode" not in body


@pytest.mark.parametrize("mode", ["live", "bg", "inbox", "webhook"])
def test_message_to_mode_non_auto_passed_through(monkeypatch, mode):
holder: dict = {}
_patch_messages_client(
monkeypatch,
holder,
responses={
("POST", "/messages"): lambda: _FakeResp(
201,
{
"id": "msg_z",
"delivery_state": "queued",
"effective_delivery_mode": mode,
},
)
},
)
result = runner.invoke(
main,
["message-to", "agt_x", "--from", "y@z", "--body", "hi", "--mode", mode],
)
assert result.exit_code == 0, result.output
body = holder["client"].calls[-1][2]
assert body["delivery_mode"] == mode
# echo_info pads labels — match the components, not the raw concat.
assert "Sent via:" in result.output
assert mode in result.output


def test_message_to_mode_invalid_value_rejected_by_click():
# Click.Choice covers validation; we just confirm the gate is in place
# so a typo doesn't silently sail past as "auto".
result = runner.invoke(
main,
["message-to", "agt_x", "--from", "y@z", "--body", "hi", "--mode", "bogus"],
)
assert result.exit_code != 0
assert "bogus" in result.output or "invalid choice" in result.output.lower()


def test_message_to_surfaces_downgraded_delivery_mode(monkeypatch):
# Server downgrade case: requested `live` but recipient has no live
# session, server delivered via inbox. CLI surfaces both the chosen
# mode and the "you asked for X" hint.
holder: dict = {}
_patch_messages_client(
monkeypatch,
holder,
responses={
("POST", "/messages"): lambda: _FakeResp(
201,
{
"id": "msg_z",
"delivery_state": "queued",
"effective_delivery_mode": "inbox",
},
)
},
)
result = runner.invoke(
main,
["message-to", "agt_x", "--from", "y@z", "--body", "hi", "--mode", "live"],
)
assert result.exit_code == 0, result.output
assert "Sent via:" in result.output
assert "inbox" in result.output
assert "requested live" in result.output


def test_message_to_omits_sent_via_when_server_does_not_return_it(monkeypatch):
# Pre-Surface-6 server (or auto + no field) returns no
# effective_delivery_mode. The CLI should not emit a "Sent via:" line.
holder: dict = {}
_patch_messages_client(
monkeypatch,
holder,
responses={
("POST", "/messages"): lambda: _FakeResp(
201, {"id": "msg_z", "delivery_state": "queued"}
)
},
)
result = runner.invoke(
main,
["message-to", "agt_x", "--from", "y@z", "--body", "hi"],
)
assert result.exit_code == 0, result.output
assert "Sent via:" not in result.output


def test_message_to_help_lists_mode_flag():
result = runner.invoke(main, ["message-to", "--help"])
assert result.exit_code == 0
assert "--mode" in result.output
for choice in ("live", "bg", "inbox", "webhook", "auto"):
assert choice in result.output