Skip to content

Commit 3a5f9d2

Browse files
Merge pull request #265 from browser-use/larsen/eng-4785-codex-fixes
fix(agent): make free Codex usable end-to-end (ENG-4785)
2 parents dd86997 + c1816b8 commit 3a5f9d2

4 files changed

Lines changed: 94 additions & 6 deletions

File tree

agent/bootstrap.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,10 @@ if [ ! -f "$CODEX_CONFIG" ] || ! grep -qE "^\[model_providers\.browser-use-free\
154154
[model_providers.browser-use-free]
155155
name = "Browser Use free (DeepSeek V4)"
156156
base_url = "$CP_BASE"
157-
wire_api = "chat"
157+
# Codex hard-removed wire_api = "chat" (Feb 2026); "responses" is the only
158+
# supported value. Codex calls {base_url}/responses, which the CP proxy
159+
# forwards to OpenRouter's drop-in Responses API. See ENG-4785.
160+
wire_api = "responses"
158161
stream_idle_timeout_ms = 300000
159162

160163
[model_providers.browser-use-free.auth]
@@ -205,6 +208,9 @@ ln -sfn /usr/local/bin/tg-schedule /usr/local/bin/schedule
205208
ln -sfn "$REPO_DIR/agent/agency-report" /usr/local/bin/agency-report
206209
ln -sfn "$REPO_DIR/agent/bux-restart" /usr/local/bin/bux-restart
207210
ln -sfn "$REPO_DIR/agent/bux-miniapp-tunnel" /usr/local/bin/bux-miniapp-tunnel
211+
# Web-terminal agent launcher: picks codex (free-DeepSeek profile or signed-in)
212+
# vs claude, so ttyd doesn't hardcode claude on a codex-only box (ENG-4785).
213+
ln -sfn "$REPO_DIR/agent/bux-agent-shell" /usr/local/bin/bux-agent-shell
208214

209215
# --- system prompt + CLAUDE.md/AGENTS.md symlinks --------------------------
210216
# The one source of truth is /home/bux/system-prompt.md (copied from the

agent/bux-agent-shell

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env bash
2+
# Launch the agent the box is actually configured for in the web terminal /
3+
# SSH session, instead of hardcoding claude (ENG-4785).
4+
#
5+
# Picks codex when the box is on the free DeepSeek path (the browser-use-free
6+
# profile is the default in ~/.codex/config.toml) or when codex is signed in;
7+
# otherwise falls back to claude. Mirrors the agent-selection logic the
8+
# telegram bot uses so the terminal and the bot agree on which agent runs.
9+
set -u
10+
11+
CODEX_CONFIG="${CODEX_CONFIG:-$HOME/.codex/config.toml}"
12+
13+
# True if the top-level `profile` key selects the free DeepSeek profile.
14+
# Top-level only: stop scanning at the first [table] header. Exact key match.
15+
free_profile_active() {
16+
[ -f "$CODEX_CONFIG" ] || return 1
17+
while IFS= read -r line; do
18+
# Strip leading whitespace so indented headers/keys are matched uniformly.
19+
trimmed="${line#"${line%%[![:space:]]*}"}"
20+
# Extract the key (text before the first '='), whitespace-trimmed, so we
21+
# match the EXACT top-level `profile` key — not `profile_dir`/`profiles`.
22+
key="${trimmed%%=*}"
23+
key="${key%"${key##*[![:space:]]}"}"
24+
case "$trimmed" in
25+
\[*) return 1 ;; # first table header -> no top-level profile line
26+
esac
27+
if [ "$key" = "profile" ] && [ "$trimmed" != "$key" ]; then
28+
val="${trimmed#*=}"
29+
val="$(printf '%s' "$val" | tr -d ' "'\''')"
30+
[ "$val" = "browser-use-free" ] && return 0 || return 1
31+
fi
32+
done < "$CODEX_CONFIG"
33+
return 1
34+
}
35+
36+
codex_signed_in() {
37+
command -v codex >/dev/null 2>&1 || return 1
38+
codex login status 2>&1 | grep -qi "logged in" && \
39+
! (codex login status 2>&1 | grep -qi "not logged in")
40+
}
41+
42+
if command -v codex >/dev/null 2>&1 && { free_profile_active || codex_signed_in; }; then
43+
exec codex "$@"
44+
fi
45+
exec claude "$@"

agent/bux-ttyd.service

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ After=network-online.target
66
Type=simple
77
User=bux
88
Group=bux
9-
ExecStart=/usr/local/bin/ttyd -i lo -p 7681 -W /usr/bin/claude
9+
ExecStart=/usr/local/bin/ttyd -i lo -p 7681 -W /usr/local/bin/bux-agent-shell
1010
Restart=always
1111
RestartSec=5
1212

agent/telegram_bot.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,6 +1395,30 @@ def _write_session_uuid(path: Path, sid: str) -> None:
13951395
_AGENT_AUTH_TTL_S = 30.0
13961396

13971397

1398+
def _codex_free_profile_active() -> bool:
1399+
"""True if config.toml selects the free DeepSeek profile as the default.
1400+
1401+
The free path (ENG-4785) needs no `codex login` — the control plane holds
1402+
the credential, the box just routes to it. So "authed" for Codex means the
1403+
top-level `profile = "browser-use-free"` line is present. Matches the exact
1404+
top-level `profile` key (not startswith), so `profile_dir`/`profiles` aren't
1405+
mistaken for the default-profile selector. Mirror of the same helper in
1406+
box_agent.py.
1407+
"""
1408+
try:
1409+
with open(CODEX_CONFIG, encoding="utf-8") as f:
1410+
for line in f:
1411+
stripped = line.strip()
1412+
if stripped.startswith("["):
1413+
break # top-level keys only — stop at the first table header
1414+
if "=" in stripped and stripped.split("=", 1)[0].strip() == "profile":
1415+
value = stripped.split("=", 1)[1].strip().strip('"').strip("'")
1416+
return value == "browser-use-free"
1417+
except Exception:
1418+
return False
1419+
return False
1420+
1421+
13981422
def _is_agent_authed(agent: str) -> bool:
13991423
"""Cheap check: is this CLI signed in? Shells out to the CLI's
14001424
`auth status` and caches the result for `_AGENT_AUTH_TTL_S` seconds.
@@ -1421,6 +1445,13 @@ def _is_agent_authed(agent: str) -> bool:
14211445
cached = _AGENT_AUTH_CACHE.get(agent)
14221446
if cached and now - cached[0] < _AGENT_AUTH_TTL_S:
14231447
return cached[1]
1448+
# Free DeepSeek path (ENG-4785): Codex has no `codex login` here — auth is
1449+
# the browser-use-free profile + bu-cp-token, so `codex login status` would
1450+
# report "not logged in" and misroute the user to Claude / the picker.
1451+
# Treat an active free profile as authed (mirrors box_agent.check_codex_authed).
1452+
if agent == AGENT_CODEX and _codex_free_profile_active():
1453+
_AGENT_AUTH_CACHE[agent] = (now, True)
1454+
return True
14241455
sub = ["auth", "status"] if agent == AGENT_CLAUDE else ["login", "status"]
14251456
cmd = ["sudo", "-iu", "bux", agent, *sub]
14261457
try:
@@ -3637,10 +3668,16 @@ def _login_status_cached(provider: str) -> bool:
36373668
except Exception:
36383669
ok = False
36393670
elif provider == "codex":
3640-
try:
3641-
ok, _ = CODEX_AUTH_PROVIDER.check()
3642-
except Exception:
3643-
ok = False
3671+
# Free DeepSeek path (ENG-4785): no `codex login`, so an active
3672+
# browser-use-free profile counts as signed in (else the picker shows
3673+
# "not signed in" for a box that's actually ready).
3674+
if _codex_free_profile_active():
3675+
ok = True
3676+
else:
3677+
try:
3678+
ok, _ = CODEX_AUTH_PROVIDER.check()
3679+
except Exception:
3680+
ok = False
36443681
else:
36453682
return False
36463683
with _login_status_lock:

0 commit comments

Comments
 (0)