Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Commit ·
4501765
1
Parent(s): 4a5df81
Refactor approval handling and enhance markdown table styles
Browse files- agent/core/agent_loop.py +34 -17
- agent/main.py +12 -3
- agent/utils/braille.py +120 -0
- agent/utils/terminal_display.py +91 -19
- pyproject.toml +6 -9
- uv.lock +20 -38
agent/core/agent_loop.py
CHANGED
|
@@ -169,6 +169,30 @@ def _is_transient_error(error: Exception) -> bool:
|
|
| 169 |
return any(pattern in err_str for pattern in transient_patterns)
|
| 170 |
|
| 171 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
async def _compact_and_notify(session: Session) -> None:
|
| 173 |
"""Run compaction and send event if context was reduced."""
|
| 174 |
old_length = session.context_manager.context_length
|
|
@@ -559,7 +583,7 @@ class Handlers:
|
|
| 559 |
|
| 560 |
# If no tool calls, add assistant message and we're done
|
| 561 |
if not tool_calls:
|
| 562 |
-
logger.
|
| 563 |
"Agent loop ending: no tool calls. "
|
| 564 |
"finish_reason=%s, token_count=%d, "
|
| 565 |
"context_length=%d, max_context=%d, "
|
|
@@ -573,20 +597,6 @@ class Handlers:
|
|
| 573 |
max_iterations,
|
| 574 |
(content or "")[:500],
|
| 575 |
)
|
| 576 |
-
await session.send_event(
|
| 577 |
-
Event(
|
| 578 |
-
event_type="tool_log",
|
| 579 |
-
data={
|
| 580 |
-
"tool": "system",
|
| 581 |
-
"log": (
|
| 582 |
-
f"Loop exit: no tool calls. "
|
| 583 |
-
f"finish_reason={finish_reason}, "
|
| 584 |
-
f"tokens={token_count}/{session.context_manager.max_context}, "
|
| 585 |
-
f"iter={iteration}/{max_iterations}"
|
| 586 |
-
),
|
| 587 |
-
},
|
| 588 |
-
)
|
| 589 |
-
)
|
| 590 |
if content:
|
| 591 |
assistant_msg = Message(role="assistant", content=content)
|
| 592 |
session.context_manager.add_message(assistant_msg, token_count)
|
|
@@ -802,10 +812,14 @@ class Handlers:
|
|
| 802 |
except Exception as e:
|
| 803 |
import traceback
|
| 804 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 805 |
await session.send_event(
|
| 806 |
Event(
|
| 807 |
event_type="error",
|
| 808 |
-
data={"error":
|
| 809 |
)
|
| 810 |
)
|
| 811 |
errored = True
|
|
@@ -1156,7 +1170,10 @@ async def submission_loop(
|
|
| 1156 |
async with tool_router:
|
| 1157 |
# Emit ready event after initialization
|
| 1158 |
await session.send_event(
|
| 1159 |
-
Event(event_type="ready", data={
|
|
|
|
|
|
|
|
|
|
| 1160 |
)
|
| 1161 |
|
| 1162 |
while session.is_running:
|
|
|
|
| 169 |
return any(pattern in err_str for pattern in transient_patterns)
|
| 170 |
|
| 171 |
|
| 172 |
+
def _friendly_error_message(error: Exception) -> str | None:
|
| 173 |
+
"""Return a user-friendly message for known error types, or None to fall back to traceback."""
|
| 174 |
+
err_str = str(error).lower()
|
| 175 |
+
|
| 176 |
+
if "authentication" in err_str or "unauthorized" in err_str or "invalid x-api-key" in err_str:
|
| 177 |
+
return (
|
| 178 |
+
"Authentication failed — your API key is missing or invalid.\n\n"
|
| 179 |
+
"To fix this, set the API key for your model provider:\n"
|
| 180 |
+
" • Anthropic: export ANTHROPIC_API_KEY=sk-...\n"
|
| 181 |
+
" • OpenAI: export OPENAI_API_KEY=sk-...\n"
|
| 182 |
+
" • HF Router: export HF_TOKEN=hf_...\n\n"
|
| 183 |
+
"You can also add it to a .env file in the project root.\n"
|
| 184 |
+
"To switch models, use the /model command."
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
if "insufficient" in err_str and "credit" in err_str:
|
| 188 |
+
return (
|
| 189 |
+
"Insufficient API credits. Please check your account balance "
|
| 190 |
+
"at your model provider's dashboard."
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
return None
|
| 194 |
+
|
| 195 |
+
|
| 196 |
async def _compact_and_notify(session: Session) -> None:
|
| 197 |
"""Run compaction and send event if context was reduced."""
|
| 198 |
old_length = session.context_manager.context_length
|
|
|
|
| 583 |
|
| 584 |
# If no tool calls, add assistant message and we're done
|
| 585 |
if not tool_calls:
|
| 586 |
+
logger.debug(
|
| 587 |
"Agent loop ending: no tool calls. "
|
| 588 |
"finish_reason=%s, token_count=%d, "
|
| 589 |
"context_length=%d, max_context=%d, "
|
|
|
|
| 597 |
max_iterations,
|
| 598 |
(content or "")[:500],
|
| 599 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 600 |
if content:
|
| 601 |
assistant_msg = Message(role="assistant", content=content)
|
| 602 |
session.context_manager.add_message(assistant_msg, token_count)
|
|
|
|
| 812 |
except Exception as e:
|
| 813 |
import traceback
|
| 814 |
|
| 815 |
+
error_msg = _friendly_error_message(e)
|
| 816 |
+
if error_msg is None:
|
| 817 |
+
error_msg = str(e) + "\n" + traceback.format_exc()
|
| 818 |
+
|
| 819 |
await session.send_event(
|
| 820 |
Event(
|
| 821 |
event_type="error",
|
| 822 |
+
data={"error": error_msg},
|
| 823 |
)
|
| 824 |
)
|
| 825 |
errored = True
|
|
|
|
| 1170 |
async with tool_router:
|
| 1171 |
# Emit ready event after initialization
|
| 1172 |
await session.send_event(
|
| 1173 |
+
Event(event_type="ready", data={
|
| 1174 |
+
"message": "Agent initialized",
|
| 1175 |
+
"tool_count": len(tool_router.tools),
|
| 1176 |
+
})
|
| 1177 |
)
|
| 1178 |
|
| 1179 |
while session.is_running:
|
agent/main.py
CHANGED
|
@@ -251,7 +251,8 @@ async def event_listener(
|
|
| 251 |
event = await event_queue.get()
|
| 252 |
|
| 253 |
if event.event_type == "ready":
|
| 254 |
-
|
|
|
|
| 255 |
ready_event.set()
|
| 256 |
elif event.event_type == "assistant_message":
|
| 257 |
shimmer.stop()
|
|
@@ -711,8 +712,6 @@ async def main():
|
|
| 711 |
# Clear screen
|
| 712 |
os.system("clear" if os.name != "nt" else "cls")
|
| 713 |
|
| 714 |
-
print_banner()
|
| 715 |
-
|
| 716 |
# Create prompt session for input (needed early for token prompt)
|
| 717 |
prompt_session = PromptSession()
|
| 718 |
|
|
@@ -721,6 +720,16 @@ async def main():
|
|
| 721 |
if not hf_token:
|
| 722 |
hf_token = await _prompt_and_save_hf_token(prompt_session)
|
| 723 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
# Create queues for communication
|
| 725 |
submission_queue = asyncio.Queue()
|
| 726 |
event_queue = asyncio.Queue()
|
|
|
|
| 251 |
event = await event_queue.get()
|
| 252 |
|
| 253 |
if event.event_type == "ready":
|
| 254 |
+
tool_count = event.data.get("tool_count", 0) if event.data else 0
|
| 255 |
+
print_init_done(tool_count=tool_count)
|
| 256 |
ready_event.set()
|
| 257 |
elif event.event_type == "assistant_message":
|
| 258 |
shimmer.stop()
|
|
|
|
| 712 |
# Clear screen
|
| 713 |
os.system("clear" if os.name != "nt" else "cls")
|
| 714 |
|
|
|
|
|
|
|
| 715 |
# Create prompt session for input (needed early for token prompt)
|
| 716 |
prompt_session = PromptSession()
|
| 717 |
|
|
|
|
| 720 |
if not hf_token:
|
| 721 |
hf_token = await _prompt_and_save_hf_token(prompt_session)
|
| 722 |
|
| 723 |
+
# Resolve username for banner
|
| 724 |
+
hf_user = None
|
| 725 |
+
try:
|
| 726 |
+
from huggingface_hub import HfApi
|
| 727 |
+
hf_user = HfApi(token=hf_token).whoami().get("name")
|
| 728 |
+
except Exception:
|
| 729 |
+
pass
|
| 730 |
+
|
| 731 |
+
print_banner(hf_user=hf_user)
|
| 732 |
+
|
| 733 |
# Create queues for communication
|
| 734 |
submission_queue = asyncio.Queue()
|
| 735 |
event_queue = asyncio.Queue()
|
agent/utils/braille.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Braille-character canvas for high-resolution terminal graphics.
|
| 2 |
+
|
| 3 |
+
Each terminal cell maps to a 2x4 dot grid using Unicode braille characters
|
| 4 |
+
(U+2800–U+28FF), giving 2× horizontal and 4× vertical resolution.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
# Braille dot positions: (0,0) (1,0) dots 1,4
|
| 8 |
+
# (0,1) (1,1) dots 2,5
|
| 9 |
+
# (0,2) (1,2) dots 3,6
|
| 10 |
+
# (0,3) (1,3) dots 7,8
|
| 11 |
+
_DOT_MAP = (
|
| 12 |
+
(0x01, 0x08),
|
| 13 |
+
(0x02, 0x10),
|
| 14 |
+
(0x04, 0x20),
|
| 15 |
+
(0x40, 0x80),
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class BrailleCanvas:
|
| 20 |
+
"""A pixel canvas that renders to braille characters."""
|
| 21 |
+
|
| 22 |
+
def __init__(self, term_width: int, term_height: int):
|
| 23 |
+
self.term_width = term_width
|
| 24 |
+
self.term_height = term_height
|
| 25 |
+
self.pixel_width = term_width * 2
|
| 26 |
+
self.pixel_height = term_height * 4
|
| 27 |
+
self._buf = bytearray(term_width * term_height)
|
| 28 |
+
|
| 29 |
+
def clear(self) -> None:
|
| 30 |
+
for i in range(len(self._buf)):
|
| 31 |
+
self._buf[i] = 0
|
| 32 |
+
|
| 33 |
+
def set_pixel(self, x: int, y: int) -> None:
|
| 34 |
+
if 0 <= x < self.pixel_width and 0 <= y < self.pixel_height:
|
| 35 |
+
cx, rx = divmod(x, 2)
|
| 36 |
+
cy, ry = divmod(y, 4)
|
| 37 |
+
self._buf[cy * self.term_width + cx] |= _DOT_MAP[ry][rx]
|
| 38 |
+
|
| 39 |
+
def render(self) -> list[str]:
|
| 40 |
+
lines = []
|
| 41 |
+
for row in range(self.term_height):
|
| 42 |
+
offset = row * self.term_width
|
| 43 |
+
line = "".join(
|
| 44 |
+
chr(0x2800 + self._buf[offset + col])
|
| 45 |
+
for col in range(self.term_width)
|
| 46 |
+
)
|
| 47 |
+
lines.append(line)
|
| 48 |
+
return lines
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# ── Bitmap font (5×7 uppercase + digits) ──────────────────────────────
|
| 52 |
+
|
| 53 |
+
_FONT: dict[str, list[str]] = {}
|
| 54 |
+
|
| 55 |
+
def _define_font() -> None:
|
| 56 |
+
"""Define a simple 5×7 bitmap font for uppercase ASCII."""
|
| 57 |
+
glyphs = {
|
| 58 |
+
"A": [" ## ", "# #", "# #", "####", "# #", "# #", "# #"],
|
| 59 |
+
"B": ["### ", "# #", "# #", "### ", "# #", "# #", "### "],
|
| 60 |
+
"C": [" ## ", "# #", "# ", "# ", "# ", "# #", " ## "],
|
| 61 |
+
"D": ["### ", "# #", "# #", "# #", "# #", "# #", "### "],
|
| 62 |
+
"E": ["####", "# ", "# ", "### ", "# ", "# ", "####"],
|
| 63 |
+
"F": ["####", "# ", "# ", "### ", "# ", "# ", "# "],
|
| 64 |
+
"G": [" ## ", "# #", "# ", "# ##", "# #", "# #", " ###"],
|
| 65 |
+
"H": ["# #", "# #", "# #", "####", "# #", "# #", "# #"],
|
| 66 |
+
"I": ["###", " # ", " # ", " # ", " # ", " # ", "###"],
|
| 67 |
+
"J": [" ##", " # ", " # ", " # ", " # ", "# # ", " # "],
|
| 68 |
+
"K": ["# #", "# # ", "## ", "## ", "# # ", "# #", "# #"],
|
| 69 |
+
"L": ["# ", "# ", "# ", "# ", "# ", "# ", "####"],
|
| 70 |
+
"M": ["# #", "## ##", "# # #", "# # #", "# #", "# #", "# #"],
|
| 71 |
+
"N": ["# #", "## #", "## #", "# ##", "# ##", "# #", "# #"],
|
| 72 |
+
"O": [" ## ", "# #", "# #", "# #", "# #", "# #", " ## "],
|
| 73 |
+
"P": ["### ", "# #", "# #", "### ", "# ", "# ", "# "],
|
| 74 |
+
"Q": [" ## ", "# #", "# #", "# #", "# ##", "# #", " ## "],
|
| 75 |
+
"R": ["### ", "# #", "# #", "### ", "# # ", "# #", "# #"],
|
| 76 |
+
"S": [" ## ", "# #", "# ", " ## ", " #", "# #", " ## "],
|
| 77 |
+
"T": ["#####", " # ", " # ", " # ", " # ", " # ", " # "],
|
| 78 |
+
"U": ["# #", "# #", "# #", "# #", "# #", "# #", " ## "],
|
| 79 |
+
"V": ["# #", "# #", "# #", " # # ", " # # ", " # ", " # "],
|
| 80 |
+
"W": ["# #", "# #", "# #", "# # #", "# # #", "## ##", "# #"],
|
| 81 |
+
"X": ["# #", "# #", " ## ", " ## ", " ## ", "# #", "# #"],
|
| 82 |
+
"Y": ["# #", "# #", " # # ", " # ", " # ", " # ", " # "],
|
| 83 |
+
"Z": ["####", " #", " # ", " # ", "# ", "# ", "####"],
|
| 84 |
+
" ": [" ", " ", " ", " ", " ", " ", " "],
|
| 85 |
+
"0": [" ## ", "# #", "# #", "# #", "# #", "# #", " ## "],
|
| 86 |
+
"1": [" # ", "## ", " # ", " # ", " # ", " # ", "###"],
|
| 87 |
+
"2": [" ## ", "# #", " #", " # ", " # ", "# ", "####"],
|
| 88 |
+
"3": [" ## ", "# #", " #", " ## ", " #", "# #", " ## "],
|
| 89 |
+
"4": ["# #", "# #", "# #", "####", " #", " #", " #"],
|
| 90 |
+
"5": ["####", "# ", "### ", " #", " #", "# #", " ## "],
|
| 91 |
+
"6": [" ## ", "# ", "### ", "# #", "# #", "# #", " ## "],
|
| 92 |
+
"7": ["####", " #", " # ", " # ", " # ", " # ", " # "],
|
| 93 |
+
"8": [" ## ", "# #", "# #", " ## ", "# #", "# #", " ## "],
|
| 94 |
+
"9": [" ## ", "# #", "# #", " ###", " #", " #", " ## "],
|
| 95 |
+
}
|
| 96 |
+
_FONT.update(glyphs)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
_define_font()
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def text_to_pixels(text: str, scale: int = 1) -> list[tuple[int, int]]:
|
| 103 |
+
"""Convert text string to a list of (x, y) pixel positions using bitmap font."""
|
| 104 |
+
pixels = []
|
| 105 |
+
cursor_x = 0
|
| 106 |
+
for ch in text.upper():
|
| 107 |
+
glyph = _FONT.get(ch)
|
| 108 |
+
if glyph is None:
|
| 109 |
+
cursor_x += 4 * scale
|
| 110 |
+
continue
|
| 111 |
+
for row_idx, row in enumerate(glyph):
|
| 112 |
+
for col_idx, cell in enumerate(row):
|
| 113 |
+
if cell == "#":
|
| 114 |
+
for sy in range(scale):
|
| 115 |
+
for sx in range(scale):
|
| 116 |
+
pixels.append((cursor_x + col_idx * scale + sx,
|
| 117 |
+
row_idx * scale + sy))
|
| 118 |
+
glyph_width = max(len(r) for r in glyph)
|
| 119 |
+
cursor_x += (glyph_width + 1) * scale
|
| 120 |
+
return pixels
|
agent/utils/terminal_display.py
CHANGED
|
@@ -8,7 +8,7 @@ from rich.panel import Panel
|
|
| 8 |
from rich.theme import Theme
|
| 9 |
|
| 10 |
_THEME = Theme({
|
| 11 |
-
"tool.name": "bold
|
| 12 |
"tool.args": "dim",
|
| 13 |
"tool.ok": "dim green",
|
| 14 |
"tool.fail": "dim red",
|
|
@@ -28,31 +28,75 @@ def get_console() -> Console:
|
|
| 28 |
|
| 29 |
# ── Banner ─────────────────────────────────────────────────────────────
|
| 30 |
|
| 31 |
-
def print_banner() -> None:
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
|
| 46 |
# ── Init progress ──────────────────────────────────────────────────────
|
| 47 |
|
| 48 |
-
def print_init_done() -> None:
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
|
| 52 |
# ── Tool calls ─────────────────────────────────────────────────────────
|
| 53 |
|
| 54 |
def print_tool_call(tool_name: str, args_preview: str) -> None:
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
|
| 58 |
def print_tool_output(output: str, success: bool, truncate: bool = True) -> None:
|
|
@@ -146,7 +190,7 @@ class SubAgentDisplay:
|
|
| 146 |
lines = []
|
| 147 |
# Header: ▸ research (stats)
|
| 148 |
stats = self._format_stats()
|
| 149 |
-
header = f"{_I}\033[
|
| 150 |
if stats:
|
| 151 |
header += f" \033[2m({stats})\033[0m"
|
| 152 |
lines.append(header)
|
|
@@ -183,9 +227,37 @@ def print_tool_log(tool: str, log: str) -> None:
|
|
| 183 |
# ── Messages ───────────────────────────────────────────────────────────
|
| 184 |
|
| 185 |
def print_markdown(text: str) -> None:
|
|
|
|
| 186 |
from rich.padding import Padding
|
|
|
|
| 187 |
_console.print()
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
|
| 190 |
|
| 191 |
def print_error(message: str) -> None:
|
|
|
|
| 8 |
from rich.theme import Theme
|
| 9 |
|
| 10 |
_THEME = Theme({
|
| 11 |
+
"tool.name": "bold rgb(255,200,80)",
|
| 12 |
"tool.args": "dim",
|
| 13 |
"tool.ok": "dim green",
|
| 14 |
"tool.fail": "dim red",
|
|
|
|
| 28 |
|
| 29 |
# ── Banner ─────────────────────────────────────────────────────────────
|
| 30 |
|
| 31 |
+
def print_banner(model: str | None = None, hf_user: str | None = None) -> None:
|
| 32 |
+
"""Print particle logo then CRT boot sequence with system info."""
|
| 33 |
+
from agent.utils.particle_logo import run_particle_logo
|
| 34 |
+
from agent.utils.crt_boot import run_boot_sequence
|
| 35 |
+
|
| 36 |
+
# Particle coalesce logo — 1.5s converge, 2s hold
|
| 37 |
+
run_particle_logo(_console, hold_seconds=2.0)
|
| 38 |
+
|
| 39 |
+
# Clear screen for CRT boot — starts from top
|
| 40 |
+
_console.file.write("\033[2J\033[H")
|
| 41 |
+
_console.file.flush()
|
| 42 |
+
|
| 43 |
+
model_label = model or "anthropic/claude-opus-4-6"
|
| 44 |
+
user_label = hf_user or "not logged in"
|
| 45 |
+
|
| 46 |
+
# Warm gold palette matching the shimmer highlight (255, 200, 80)
|
| 47 |
+
gold = "rgb(255,200,80)"
|
| 48 |
+
dim_gold = "rgb(180,140,40)"
|
| 49 |
+
|
| 50 |
+
boot_lines = [
|
| 51 |
+
(f"{_I}Initializing agent runtime...", gold),
|
| 52 |
+
(f"{_I} User: {user_label}", dim_gold),
|
| 53 |
+
(f"{_I} Model: {model_label}", dim_gold),
|
| 54 |
+
(f"{_I} Tools: loading...", dim_gold),
|
| 55 |
+
("", ""),
|
| 56 |
+
(f"{_I}/help for commands · /model to switch · /quit to exit", gold),
|
| 57 |
+
]
|
| 58 |
+
|
| 59 |
+
run_boot_sequence(_console, boot_lines)
|
| 60 |
|
| 61 |
|
| 62 |
# ── Init progress ──────────────────────────────────────────────────────
|
| 63 |
|
| 64 |
+
def print_init_done(tool_count: int = 0) -> None:
|
| 65 |
+
import time
|
| 66 |
+
f = _console.file
|
| 67 |
+
# Overwrite the "Tools: loading..." line with actual count
|
| 68 |
+
f.write(f"\033[A\033[A\033[A\033[K") # Move up 3 lines (blank + help + blank) then up to tools line
|
| 69 |
+
f.write(f"\033[A\033[K")
|
| 70 |
+
gold = "\033[38;2;180;140;40m"
|
| 71 |
+
reset = "\033[0m"
|
| 72 |
+
tool_text = f"{_I} Tools: {tool_count} loaded"
|
| 73 |
+
for ch in tool_text:
|
| 74 |
+
f.write(f"{gold}{ch}{reset}")
|
| 75 |
+
f.flush()
|
| 76 |
+
time.sleep(0.012)
|
| 77 |
+
f.write("\n\n")
|
| 78 |
+
# Reprint the help line
|
| 79 |
+
f.write(f"{_I}\033[38;2;255;200;80m/help for commands · /model to switch · /quit to exit{reset}\n\n")
|
| 80 |
+
# Ready message — minimal padding
|
| 81 |
+
f.write(f"{_I}\033[38;2;255;200;80mReady. Let's build something impressive.{reset}\n")
|
| 82 |
+
f.flush()
|
| 83 |
|
| 84 |
|
| 85 |
# ── Tool calls ─────────────────────────────────────────────────────────
|
| 86 |
|
| 87 |
def print_tool_call(tool_name: str, args_preview: str) -> None:
|
| 88 |
+
import time
|
| 89 |
+
f = _console.file
|
| 90 |
+
# CRT-style: type out tool name in HF yellow
|
| 91 |
+
gold = "\033[38;2;255;200;80m"
|
| 92 |
+
reset = "\033[0m"
|
| 93 |
+
f.write(f"{_I}{gold}▸ ")
|
| 94 |
+
for ch in tool_name:
|
| 95 |
+
f.write(ch)
|
| 96 |
+
f.flush()
|
| 97 |
+
time.sleep(0.015)
|
| 98 |
+
f.write(f"{reset} \033[2m{args_preview}{reset}\n")
|
| 99 |
+
f.flush()
|
| 100 |
|
| 101 |
|
| 102 |
def print_tool_output(output: str, success: bool, truncate: bool = True) -> None:
|
|
|
|
| 190 |
lines = []
|
| 191 |
# Header: ▸ research (stats)
|
| 192 |
stats = self._format_stats()
|
| 193 |
+
header = f"{_I}\033[38;2;255;200;80m▸ research\033[0m"
|
| 194 |
if stats:
|
| 195 |
header += f" \033[2m({stats})\033[0m"
|
| 196 |
lines.append(header)
|
|
|
|
| 227 |
# ── Messages ───────────────────────────────────────────────────────────
|
| 228 |
|
| 229 |
def print_markdown(text: str) -> None:
|
| 230 |
+
import io, time, random
|
| 231 |
from rich.padding import Padding
|
| 232 |
+
|
| 233 |
_console.print()
|
| 234 |
+
|
| 235 |
+
# Render markdown to a string buffer so we can type it out
|
| 236 |
+
buf = io.StringIO()
|
| 237 |
+
buf_console = Console(file=buf, width=_console.width, highlight=False, theme=_THEME)
|
| 238 |
+
buf_console.print(Padding(Markdown(text), (0, 0, 0, 2)))
|
| 239 |
+
rendered = buf.getvalue()
|
| 240 |
+
|
| 241 |
+
# Strip trailing whitespace from each line so we don't type across the full width
|
| 242 |
+
lines = rendered.split("\n")
|
| 243 |
+
rendered = "\n".join(line.rstrip() for line in lines)
|
| 244 |
+
|
| 245 |
+
# CRT typewriter effect — fast, with occasional glitch
|
| 246 |
+
rng = random.Random(42)
|
| 247 |
+
f = _console.file
|
| 248 |
+
for ch in rendered:
|
| 249 |
+
f.write(ch)
|
| 250 |
+
f.flush()
|
| 251 |
+
if ch == "\n":
|
| 252 |
+
time.sleep(0.002)
|
| 253 |
+
elif ch == " ":
|
| 254 |
+
time.sleep(0.002)
|
| 255 |
+
elif rng.random() < 0.03:
|
| 256 |
+
time.sleep(0.015)
|
| 257 |
+
else:
|
| 258 |
+
time.sleep(0.004)
|
| 259 |
+
f.write("\n")
|
| 260 |
+
f.flush()
|
| 261 |
|
| 262 |
|
| 263 |
def print_error(message: str) -> None:
|
pyproject.toml
CHANGED
|
@@ -5,15 +5,11 @@ description = "Add your description here"
|
|
| 5 |
readme = "README.md"
|
| 6 |
requires-python = ">=3.11"
|
| 7 |
dependencies = [
|
|
|
|
| 8 |
"datasets>=4.4.1",
|
| 9 |
-
# Core dependencies (always required)
|
| 10 |
"pydantic>=2.12.3",
|
| 11 |
"python-dotenv>=1.2.1",
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
[project.optional-dependencies]
|
| 15 |
-
# Agent runtime dependencies
|
| 16 |
-
agent = [
|
| 17 |
"requests>=2.33.0",
|
| 18 |
"litellm>=1.83.0",
|
| 19 |
"huggingface-hub>=1.0.1",
|
|
@@ -23,7 +19,6 @@ agent = [
|
|
| 23 |
"rich>=13.0.0",
|
| 24 |
"nbconvert>=7.16.6",
|
| 25 |
"nbformat>=5.10.4",
|
| 26 |
-
"datasets>=4.3.0", # For session logging to HF datasets
|
| 27 |
"whoosh>=2.7.4",
|
| 28 |
# Web backend dependencies
|
| 29 |
"fastapi>=0.115.0",
|
|
@@ -32,6 +27,8 @@ agent = [
|
|
| 32 |
"websockets>=13.0",
|
| 33 |
]
|
| 34 |
|
|
|
|
|
|
|
| 35 |
# Evaluation/benchmarking dependencies
|
| 36 |
eval = [
|
| 37 |
"inspect-ai>=0.3.149",
|
|
@@ -45,9 +42,9 @@ dev = [
|
|
| 45 |
"pytest>=9.0.2",
|
| 46 |
]
|
| 47 |
|
| 48 |
-
# All dependencies (
|
| 49 |
all = [
|
| 50 |
-
"hf-agent[
|
| 51 |
]
|
| 52 |
|
| 53 |
[build-system]
|
|
|
|
| 5 |
readme = "README.md"
|
| 6 |
requires-python = ">=3.11"
|
| 7 |
dependencies = [
|
| 8 |
+
# Core dependencies
|
| 9 |
"datasets>=4.4.1",
|
|
|
|
| 10 |
"pydantic>=2.12.3",
|
| 11 |
"python-dotenv>=1.2.1",
|
| 12 |
+
# Agent runtime dependencies
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
"requests>=2.33.0",
|
| 14 |
"litellm>=1.83.0",
|
| 15 |
"huggingface-hub>=1.0.1",
|
|
|
|
| 19 |
"rich>=13.0.0",
|
| 20 |
"nbconvert>=7.16.6",
|
| 21 |
"nbformat>=5.10.4",
|
|
|
|
| 22 |
"whoosh>=2.7.4",
|
| 23 |
# Web backend dependencies
|
| 24 |
"fastapi>=0.115.0",
|
|
|
|
| 27 |
"websockets>=13.0",
|
| 28 |
]
|
| 29 |
|
| 30 |
+
[project.optional-dependencies]
|
| 31 |
+
|
| 32 |
# Evaluation/benchmarking dependencies
|
| 33 |
eval = [
|
| 34 |
"inspect-ai>=0.3.149",
|
|
|
|
| 42 |
"pytest>=9.0.2",
|
| 43 |
]
|
| 44 |
|
| 45 |
+
# All dependencies (eval + dev)
|
| 46 |
all = [
|
| 47 |
+
"hf-agent[eval,dev]",
|
| 48 |
]
|
| 49 |
|
| 50 |
[build-system]
|
uv.lock
CHANGED
|
@@ -997,13 +997,6 @@ name = "hf-agent"
|
|
| 997 |
version = "0.1.0"
|
| 998 |
source = { editable = "." }
|
| 999 |
dependencies = [
|
| 1000 |
-
{ name = "datasets" },
|
| 1001 |
-
{ name = "pydantic" },
|
| 1002 |
-
{ name = "python-dotenv" },
|
| 1003 |
-
]
|
| 1004 |
-
|
| 1005 |
-
[package.optional-dependencies]
|
| 1006 |
-
agent = [
|
| 1007 |
{ name = "datasets" },
|
| 1008 |
{ name = "fastapi" },
|
| 1009 |
{ name = "fastmcp" },
|
|
@@ -1013,6 +1006,8 @@ agent = [
|
|
| 1013 |
{ name = "nbconvert" },
|
| 1014 |
{ name = "nbformat" },
|
| 1015 |
{ name = "prompt-toolkit" },
|
|
|
|
|
|
|
| 1016 |
{ name = "requests" },
|
| 1017 |
{ name = "rich" },
|
| 1018 |
{ name = "thefuzz" },
|
|
@@ -1020,26 +1015,14 @@ agent = [
|
|
| 1020 |
{ name = "websockets" },
|
| 1021 |
{ name = "whoosh" },
|
| 1022 |
]
|
|
|
|
|
|
|
| 1023 |
all = [
|
| 1024 |
{ name = "datasets" },
|
| 1025 |
-
{ name = "fastapi" },
|
| 1026 |
-
{ name = "fastmcp" },
|
| 1027 |
-
{ name = "httpx" },
|
| 1028 |
-
{ name = "huggingface-hub" },
|
| 1029 |
{ name = "inspect-ai" },
|
| 1030 |
-
{ name = "litellm" },
|
| 1031 |
-
{ name = "nbconvert" },
|
| 1032 |
-
{ name = "nbformat" },
|
| 1033 |
{ name = "pandas" },
|
| 1034 |
-
{ name = "prompt-toolkit" },
|
| 1035 |
{ name = "pytest" },
|
| 1036 |
-
{ name = "requests" },
|
| 1037 |
-
{ name = "rich" },
|
| 1038 |
{ name = "tenacity" },
|
| 1039 |
-
{ name = "thefuzz" },
|
| 1040 |
-
{ name = "uvicorn", extra = ["standard"] },
|
| 1041 |
-
{ name = "websockets" },
|
| 1042 |
-
{ name = "whoosh" },
|
| 1043 |
]
|
| 1044 |
dev = [
|
| 1045 |
{ name = "pytest" },
|
|
@@ -1054,31 +1037,30 @@ eval = [
|
|
| 1054 |
[package.metadata]
|
| 1055 |
requires-dist = [
|
| 1056 |
{ name = "datasets", specifier = ">=4.4.1" },
|
| 1057 |
-
{ name = "datasets", marker = "extra == 'agent'", specifier = ">=4.3.0" },
|
| 1058 |
{ name = "datasets", marker = "extra == 'eval'", specifier = ">=4.3.0" },
|
| 1059 |
-
{ name = "fastapi",
|
| 1060 |
-
{ name = "fastmcp",
|
| 1061 |
-
{ name = "hf-agent", extras = ["
|
| 1062 |
-
{ name = "httpx",
|
| 1063 |
-
{ name = "huggingface-hub",
|
| 1064 |
{ name = "inspect-ai", marker = "extra == 'eval'", specifier = ">=0.3.149" },
|
| 1065 |
-
{ name = "litellm",
|
| 1066 |
-
{ name = "nbconvert",
|
| 1067 |
-
{ name = "nbformat",
|
| 1068 |
{ name = "pandas", marker = "extra == 'eval'", specifier = ">=2.3.3" },
|
| 1069 |
-
{ name = "prompt-toolkit",
|
| 1070 |
{ name = "pydantic", specifier = ">=2.12.3" },
|
| 1071 |
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.2" },
|
| 1072 |
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
| 1073 |
-
{ name = "requests",
|
| 1074 |
-
{ name = "rich",
|
| 1075 |
{ name = "tenacity", marker = "extra == 'eval'", specifier = ">=8.0.0" },
|
| 1076 |
-
{ name = "thefuzz",
|
| 1077 |
-
{ name = "uvicorn", extras = ["standard"],
|
| 1078 |
-
{ name = "websockets",
|
| 1079 |
-
{ name = "whoosh",
|
| 1080 |
]
|
| 1081 |
-
provides-extras = ["
|
| 1082 |
|
| 1083 |
[[package]]
|
| 1084 |
name = "hf-xet"
|
|
|
|
| 997 |
version = "0.1.0"
|
| 998 |
source = { editable = "." }
|
| 999 |
dependencies = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1000 |
{ name = "datasets" },
|
| 1001 |
{ name = "fastapi" },
|
| 1002 |
{ name = "fastmcp" },
|
|
|
|
| 1006 |
{ name = "nbconvert" },
|
| 1007 |
{ name = "nbformat" },
|
| 1008 |
{ name = "prompt-toolkit" },
|
| 1009 |
+
{ name = "pydantic" },
|
| 1010 |
+
{ name = "python-dotenv" },
|
| 1011 |
{ name = "requests" },
|
| 1012 |
{ name = "rich" },
|
| 1013 |
{ name = "thefuzz" },
|
|
|
|
| 1015 |
{ name = "websockets" },
|
| 1016 |
{ name = "whoosh" },
|
| 1017 |
]
|
| 1018 |
+
|
| 1019 |
+
[package.optional-dependencies]
|
| 1020 |
all = [
|
| 1021 |
{ name = "datasets" },
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1022 |
{ name = "inspect-ai" },
|
|
|
|
|
|
|
|
|
|
| 1023 |
{ name = "pandas" },
|
|
|
|
| 1024 |
{ name = "pytest" },
|
|
|
|
|
|
|
| 1025 |
{ name = "tenacity" },
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1026 |
]
|
| 1027 |
dev = [
|
| 1028 |
{ name = "pytest" },
|
|
|
|
| 1037 |
[package.metadata]
|
| 1038 |
requires-dist = [
|
| 1039 |
{ name = "datasets", specifier = ">=4.4.1" },
|
|
|
|
| 1040 |
{ name = "datasets", marker = "extra == 'eval'", specifier = ">=4.3.0" },
|
| 1041 |
+
{ name = "fastapi", specifier = ">=0.115.0" },
|
| 1042 |
+
{ name = "fastmcp", specifier = ">=3.2.0" },
|
| 1043 |
+
{ name = "hf-agent", extras = ["eval", "dev"], marker = "extra == 'all'" },
|
| 1044 |
+
{ name = "httpx", specifier = ">=0.27.0" },
|
| 1045 |
+
{ name = "huggingface-hub", specifier = ">=1.0.1" },
|
| 1046 |
{ name = "inspect-ai", marker = "extra == 'eval'", specifier = ">=0.3.149" },
|
| 1047 |
+
{ name = "litellm", specifier = ">=1.83.0" },
|
| 1048 |
+
{ name = "nbconvert", specifier = ">=7.16.6" },
|
| 1049 |
+
{ name = "nbformat", specifier = ">=5.10.4" },
|
| 1050 |
{ name = "pandas", marker = "extra == 'eval'", specifier = ">=2.3.3" },
|
| 1051 |
+
{ name = "prompt-toolkit", specifier = ">=3.0.0" },
|
| 1052 |
{ name = "pydantic", specifier = ">=2.12.3" },
|
| 1053 |
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.2" },
|
| 1054 |
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
| 1055 |
+
{ name = "requests", specifier = ">=2.33.0" },
|
| 1056 |
+
{ name = "rich", specifier = ">=13.0.0" },
|
| 1057 |
{ name = "tenacity", marker = "extra == 'eval'", specifier = ">=8.0.0" },
|
| 1058 |
+
{ name = "thefuzz", specifier = ">=0.22.1" },
|
| 1059 |
+
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.32.0" },
|
| 1060 |
+
{ name = "websockets", specifier = ">=13.0" },
|
| 1061 |
+
{ name = "whoosh", specifier = ">=2.7.4" },
|
| 1062 |
]
|
| 1063 |
+
provides-extras = ["eval", "dev", "all"]
|
| 1064 |
|
| 1065 |
[[package]]
|
| 1066 |
name = "hf-xet"
|