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
65 changes: 52 additions & 13 deletions agent/telegram_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ def random_thinking_reaction() -> str:
DEFAULT_AGENT = AGENT_CLAUDE
CODEX_REASONING_EFFORTS = ("low", "medium", "high", "xhigh")
CODEX_MODEL_CHOICES = ("gpt-5.5", "gpt-5.4", "gpt-5.4-mini")
CODEX_FAST_SERVICE_TIER = "priority"


# Registered with Telegram via setMyCommands at boot. Order = order shown
Expand Down Expand Up @@ -1042,7 +1043,7 @@ def burn_setup_token() -> None:
# {
# "offset": <int>,
# "agents": {"<lane_slug>": "claude"|"codex"},
# "codex_settings": {"<lane_slug>": {"model": "...", "reasoning_effort": "..."}}
# "codex_settings": {"<lane_slug>": {"model": "...", "reasoning_effort": "...", "service_tier": "..."}}
# }
# ---------------------------------------------------------------------------

Expand Down Expand Up @@ -1190,6 +1191,8 @@ def _codex_interactive_command_for(key: LaneKey, state: dict, prompt: str | None
args += ["-m", settings["model"]]
if settings.get("reasoning_effort"):
args += ["-c", f'model_reasoning_effort="{settings["reasoning_effort"]}"']
if settings.get("service_tier"):
args += ["-c", f'service_tier="{settings["service_tier"]}"']
thread_id = _codex_thread_id_for(key)
if thread_id:
args += ["resume", "--include-non-interactive", thread_id]
Expand Down Expand Up @@ -1394,6 +1397,9 @@ def _codex_settings_for(key: LaneKey, state: dict) -> dict:
effort = str(raw.get("reasoning_effort") or "").strip().lower()
if effort in CODEX_REASONING_EFFORTS:
out["reasoning_effort"] = effort
service_tier = str(raw.get("service_tier") or "").strip().lower()
if service_tier == CODEX_FAST_SERVICE_TIER:
out["service_tier"] = service_tier
return out


Expand All @@ -1403,6 +1409,7 @@ def _set_codex_settings(
*,
model: str | None = None,
reasoning_effort: str | None = None,
service_tier: str | None = None,
clear: bool = False,
) -> dict:
slug = _lane_slug(key)
Expand All @@ -1424,6 +1431,12 @@ def _set_codex_settings(
current["reasoning_effort"] = reasoning_effort
elif not reasoning_effort:
current.pop("reasoning_effort", None)
if service_tier is not None:
service_tier = service_tier.strip().lower()
if service_tier == CODEX_FAST_SERVICE_TIER:
current["service_tier"] = service_tier
elif not service_tier:
current.pop("service_tier", None)
if current:
settings_by_lane[slug] = current
else:
Expand All @@ -1435,12 +1448,14 @@ def _set_codex_settings(
def _format_codex_settings(settings: dict) -> str:
model = settings.get("model") or "(Codex default)"
effort = settings.get("reasoning_effort") or "(Codex default)"
return f"model: `{model}`\nreasoning effort: `{effort}`"
fast = "on" if settings.get("service_tier") == CODEX_FAST_SERVICE_TIER else "off"
return f"model: `{model}`\nreasoning effort: `{effort}`\nfast mode: `{fast}`"


def _codex_model_picker_markup(settings: dict) -> dict:
current_model = settings.get("model") or ""
current_effort = settings.get("reasoning_effort") or ""
fast_enabled = settings.get("service_tier") == CODEX_FAST_SERVICE_TIER

def label(text: str, active: bool) -> str:
return f"✓ {text}" if active else text
Expand All @@ -1456,26 +1471,22 @@ def label(text: str, active: bool) -> str:
"text": label(model.replace("gpt-", ""), current_model == model),
"callback_data": f"codex_model:model:{model}",
})
effort_labels = {
"low": "Fast",
"medium": "Medium",
"high": "High",
"xhigh": "XHigh",
}
effort_row = [
{
"text": label("Default", not current_effort),
"callback_data": "codex_model:effort:default",
}
] + [
{
"text": label(effort_labels[effort], current_effort == effort),
"text": label(effort.title(), current_effort == effort),
"callback_data": f"codex_model:effort:{effort}",
}
for effort in CODEX_REASONING_EFFORTS
]
return {
"inline_keyboard": [
[{"text": "Fast mode: on" if fast_enabled else "Fast mode: off",
"callback_data": "codex_model:fast:toggle"}],
model_row,
effort_row,
[{"text": "Reset Codex defaults", "callback_data": "codex_model:reset"}],
Expand Down Expand Up @@ -4775,6 +4786,11 @@ def _run_codex(
"-c",
f'model_reasoning_effort="{codex_settings["reasoning_effort"]}"',
]
if codex_settings.get("service_tier"):
codex_bypass_flags += [
"-c",
f'service_tier="{codex_settings["service_tier"]}"',
]
if existing_thread:
# Resume the lane's existing codex thread so conversation context
# carries across messages. `codex exec resume <id>` is the
Expand Down Expand Up @@ -6041,10 +6057,17 @@ def handle(self, msg: dict) -> None:
return
if cmd == "/fast":
_set_agent_for(key, AGENT_CODEX, self.state)
settings = _set_codex_settings(key, self.state, reasoning_effort="low")
current = _codex_settings_for(key, self.state)
turning_on = current.get("service_tier") != CODEX_FAST_SERVICE_TIER
settings = _set_codex_settings(
key,
self.state,
service_tier=CODEX_FAST_SERVICE_TIER if turning_on else "",
)
self.send(
chat_id,
"Fast mode on.\n\n" + _format_codex_settings(settings),
("Fast mode on.\n\n" if turning_on else "Fast mode off.\n\n")
+ _format_codex_settings(settings),
reply_to=mid,
thread_id=thread_id,
markdown=True,
Expand Down Expand Up @@ -6480,10 +6503,17 @@ def _cmd_codex_model(

if first == "fast":
_set_agent_for(key, AGENT_CODEX, self.state)
settings = _set_codex_settings(key, self.state, reasoning_effort="low")
current = _codex_settings_for(key, self.state)
turning_on = current.get("service_tier") != CODEX_FAST_SERVICE_TIER
settings = _set_codex_settings(
key,
self.state,
service_tier=CODEX_FAST_SERVICE_TIER if turning_on else "",
)
self.send(
chat_id,
"Fast mode on.\n\n" + _format_codex_settings(settings),
("Fast mode on.\n\n" if turning_on else "Fast mode off.\n\n")
+ _format_codex_settings(settings),
reply_to=reply_to,
thread_id=thread_id,
markdown=True,
Expand Down Expand Up @@ -7466,6 +7496,15 @@ def _handle_codex_model_callback(self, cb: dict, data: str) -> None:
if action == "reset":
settings = _set_codex_settings(key, self.state, clear=True)
toast = "Codex defaults restored"
elif action == "fast" and value == "toggle":
current = _codex_settings_for(key, self.state)
turning_on = current.get("service_tier") != CODEX_FAST_SERVICE_TIER
settings = _set_codex_settings(
key,
self.state,
service_tier=CODEX_FAST_SERVICE_TIER if turning_on else "",
)
toast = "Fast mode on" if turning_on else "Fast mode off"
elif action == "model":
model = "" if value == "default" else value
settings = _set_codex_settings(key, self.state, model=model)
Expand Down
96 changes: 91 additions & 5 deletions agent/test_telegram_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ def test_codex_settings_are_per_lane(self) -> None:
state,
model="gpt-5.4-mini",
reasoning_effort="low",
service_tier="priority",
)

self.assertEqual(
telegram_bot._codex_settings_for(first, state),
{"model": "gpt-5.4-mini", "reasoning_effort": "low"},
{"model": "gpt-5.4-mini", "reasoning_effort": "low", "service_tier": "priority"},
)
self.assertEqual(telegram_bot._codex_settings_for(second, state), {})

Expand Down Expand Up @@ -64,9 +65,21 @@ def test_invalid_effort_is_ignored(self) -> None:

self.assertEqual(settings, {"model": "gpt-5.4"})

def test_invalid_service_tier_is_ignored(self) -> None:
state = {"offset": 0, "agents": {}, "codex_settings": {}, "owners": {}}

with mock.patch.object(telegram_bot, "save_state"):
settings = telegram_bot._set_codex_settings(
(1, 10),
state,
service_tier="turbo",
)

self.assertEqual(settings, {})

def test_model_picker_marks_current_choices(self) -> None:
markup = telegram_bot._codex_model_picker_markup(
{"model": "gpt-5.4", "reasoning_effort": "high"}
{"model": "gpt-5.4", "reasoning_effort": "high", "service_tier": "priority"}
)

labels = [
Expand All @@ -76,7 +89,7 @@ def test_model_picker_marks_current_choices(self) -> None:
]
self.assertIn("✓ 5.4", labels)
self.assertIn("✓ High", labels)
self.assertIn("Fast", labels)
self.assertIn("Fast mode: on", labels)
self.assertIn("Reset Codex defaults", labels)

def test_model_picker_callback_updates_effort_in_place(self) -> None:
Expand Down Expand Up @@ -117,7 +130,44 @@ def fake_call(method: str, **kwargs):
self.assertEqual(bot.state["agents"]["100_123"], "codex")
self.assertTrue(any(method == "editMessageText" for method, _ in calls))

def test_plain_fast_switches_codex_effort(self) -> None:
def test_fast_callback_toggles_service_tier(self) -> None:
bot = telegram_bot.Bot.__new__(telegram_bot.Bot)
bot.state = {
"offset": 0,
"agents": {},
"codex_settings": {},
"owners": {"100": {"user_id": "55", "name": "Magnus"}},
}
calls: list[tuple[str, dict]] = []

def fake_call(method: str, **kwargs):
calls.append((method, kwargs))
return {"ok": True}

bot.call = fake_call # type: ignore[method-assign]
bot.send = lambda *_args, **_kwargs: None # type: ignore[method-assign]

with mock.patch.object(telegram_bot, "save_state"):
bot._handle_codex_model_callback(
{
"id": "cb1",
"from": {"id": 55, "username": "Magnus_Mueller"},
"message": {
"chat": {"id": 100},
"message_id": 99,
"message_thread_id": 123,
},
},
"codex_model:fast:toggle",
)

self.assertEqual(
bot.state["codex_settings"]["100_123"],
{"service_tier": "priority"},
)
self.assertTrue(any(method == "editMessageText" for method, _ in calls))

def test_plain_fast_switches_codex_fast_service_tier(self) -> None:
sent: list[tuple[str, dict]] = []
bot = telegram_bot.Bot.__new__(telegram_bot.Bot)
bot.state = {"offset": 0, "agents": {}, "codex_settings": {}, "owners": {}}
Expand Down Expand Up @@ -145,11 +195,47 @@ def test_plain_fast_switches_codex_effort(self) -> None:
self.assertEqual(bot.state["agents"]["100_main"], "codex")
self.assertEqual(
bot.state["codex_settings"]["100_main"],
{"reasoning_effort": "low"},
{"service_tier": "priority"},
)
self.assertIn("Fast mode on.", sent[-1][0])
self.assertNotIn("reply_markup", sent[-1][1])

def test_plain_fast_toggles_fast_service_tier_off(self) -> None:
sent: list[tuple[str, dict]] = []
bot = telegram_bot.Bot.__new__(telegram_bot.Bot)
bot.state = {
"offset": 0,
"agents": {},
"codex_settings": {"100_main": {"service_tier": "priority", "reasoning_effort": "xhigh"}},
"owners": {},
}
bot.setup_token = None
bot._username = "bux_bot"
bot.react = lambda *_args, **_kwargs: None # type: ignore[method-assign]
bot.typing = lambda *_args, **_kwargs: None # type: ignore[method-assign]
bot.send = lambda _chat, text, **kwargs: sent.append((text, kwargs)) # type: ignore[method-assign]

with (
mock.patch.object(telegram_bot, "load_allow", return_value={100}),
mock.patch.object(telegram_bot, "save_state"),
mock.patch.object(telegram_bot, "_get_shell_session", return_value=None),
mock.patch.object(telegram_bot, "_goal_state_for", return_value=None),
):
bot.handle(
{
"chat": {"id": 100, "type": "private"},
"from": {"id": 55, "username": "Magnus_Mueller"},
"message_id": 123,
"text": "fast",
}
)

self.assertEqual(
bot.state["codex_settings"]["100_main"],
{"reasoning_effort": "xhigh"},
)
self.assertIn("Fast mode off.", sent[-1][0])

def test_codex_goal_feature_enablement_is_idempotent(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
config = Path(tmp) / "config.toml"
Expand Down
Loading