mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 23:11:37 +08:00
Compare commits
2 Commits
codex-port
...
macbook-te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
265562f240 | ||
|
|
95c55fa2e9 |
117
run_agent.py
117
run_agent.py
@@ -360,6 +360,10 @@ class AIAgent:
|
|||||||
"(ノ´ヮ`)ノ*:・゚✧", "ヽ(>∀<☆)ノ", "(☆▽☆)", "( ˘▽˘)っ", "(≧◡≦)",
|
"(ノ´ヮ`)ノ*:・゚✧", "ヽ(>∀<☆)ノ", "(☆▽☆)", "( ˘▽˘)っ", "(≧◡≦)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def _format_status(self, emoji: str, message: str, time_str: str, face: str) -> str:
|
||||||
|
"""Format status with fixed alignment: ● │ emoji message time face"""
|
||||||
|
return f"● │ {emoji} {message} {time_str} {face}"
|
||||||
|
|
||||||
def _get_cute_tool_message(self, tool_name: str, args: dict, duration: float) -> str:
|
def _get_cute_tool_message(self, tool_name: str, args: dict, duration: float) -> str:
|
||||||
"""
|
"""
|
||||||
Generate a kawaii ASCII/unicode art message for tool execution in CLI mode.
|
Generate a kawaii ASCII/unicode art message for tool execution in CLI mode.
|
||||||
@@ -377,10 +381,10 @@ class AIAgent:
|
|||||||
# Web tools - show what we're searching/reading
|
# Web tools - show what we're searching/reading
|
||||||
if tool_name == "web_search":
|
if tool_name == "web_search":
|
||||||
query = args.get("query", "the web")
|
query = args.get("query", "the web")
|
||||||
if len(query) > 40:
|
if len(query) > 35:
|
||||||
query = query[:37] + "..."
|
query = query[:32] + "..."
|
||||||
face = random.choice(self.KAWAII_SEARCH)
|
face = random.choice(self.KAWAII_SEARCH)
|
||||||
return f"{face} 🔍 Searching for '{query}'... {time_str}"
|
return self._format_status("🔍", f"searched '{query}'", time_str, face)
|
||||||
|
|
||||||
elif tool_name == "web_extract":
|
elif tool_name == "web_extract":
|
||||||
urls = args.get("urls", [])
|
urls = args.get("urls", [])
|
||||||
@@ -388,67 +392,67 @@ class AIAgent:
|
|||||||
if urls:
|
if urls:
|
||||||
url = urls[0] if isinstance(urls, list) else str(urls)
|
url = urls[0] if isinstance(urls, list) else str(urls)
|
||||||
domain = url.replace("https://", "").replace("http://", "").split("/")[0]
|
domain = url.replace("https://", "").replace("http://", "").split("/")[0]
|
||||||
if len(domain) > 25:
|
if len(domain) > 20:
|
||||||
domain = domain[:22] + "..."
|
domain = domain[:17] + "..."
|
||||||
if len(urls) > 1:
|
if len(urls) > 1:
|
||||||
return f"{face} 📖 Reading {domain} +{len(urls)-1} more... {time_str}"
|
return self._format_status("📖", f"read {domain} +{len(urls)-1} more", time_str, face)
|
||||||
return f"{face} 📖 Reading {domain}... {time_str}"
|
return self._format_status("📖", f"read {domain}", time_str, face)
|
||||||
return f"{face} 📖 Reading pages... {time_str}"
|
return self._format_status("📖", "read pages", time_str, face)
|
||||||
|
|
||||||
elif tool_name == "web_crawl":
|
elif tool_name == "web_crawl":
|
||||||
url = args.get("url", "website")
|
url = args.get("url", "website")
|
||||||
domain = url.replace("https://", "").replace("http://", "").split("/")[0]
|
domain = url.replace("https://", "").replace("http://", "").split("/")[0]
|
||||||
if len(domain) > 25:
|
if len(domain) > 20:
|
||||||
domain = domain[:22] + "..."
|
domain = domain[:17] + "..."
|
||||||
face = random.choice(self.KAWAII_READ)
|
face = random.choice(self.KAWAII_READ)
|
||||||
return f"{face} 🕸️ Crawling {domain}... {time_str}"
|
return self._format_status("🕸️", f"crawled {domain}", time_str, face)
|
||||||
|
|
||||||
# Terminal tool
|
# Terminal tool
|
||||||
elif tool_name == "terminal":
|
elif tool_name == "terminal":
|
||||||
command = args.get("command", "")
|
command = args.get("command", "")
|
||||||
if len(command) > 30:
|
if len(command) > 35:
|
||||||
command = command[:27] + "..."
|
command = command[:32] + "..."
|
||||||
face = random.choice(self.KAWAII_TERMINAL)
|
face = random.choice(self.KAWAII_TERMINAL)
|
||||||
return f"{face} 💻 $ {command} {time_str}"
|
return self._format_status("💻", f"$ {command}", time_str, face)
|
||||||
|
|
||||||
# Browser tools
|
# Browser tools
|
||||||
elif tool_name == "browser_navigate":
|
elif tool_name == "browser_navigate":
|
||||||
url = args.get("url", "page")
|
url = args.get("url", "page")
|
||||||
domain = url.replace("https://", "").replace("http://", "").split("/")[0]
|
domain = url.replace("https://", "").replace("http://", "").split("/")[0]
|
||||||
if len(domain) > 25:
|
if len(domain) > 20:
|
||||||
domain = domain[:22] + "..."
|
domain = domain[:17] + "..."
|
||||||
face = random.choice(self.KAWAII_BROWSER)
|
face = random.choice(self.KAWAII_BROWSER)
|
||||||
return f"{face} 🌐 → {domain} {time_str}"
|
return self._format_status("🌐", f"→ {domain}", time_str, face)
|
||||||
|
|
||||||
elif tool_name == "browser_snapshot":
|
elif tool_name == "browser_snapshot":
|
||||||
face = random.choice(self.KAWAII_BROWSER)
|
face = random.choice(self.KAWAII_BROWSER)
|
||||||
return f"{face} 📸 *snap* {time_str}"
|
return self._format_status("📸", "*snap*", time_str, face)
|
||||||
|
|
||||||
elif tool_name == "browser_click":
|
elif tool_name == "browser_click":
|
||||||
element = args.get("ref", "element")
|
element = args.get("ref", "element")
|
||||||
face = random.choice(self.KAWAII_BROWSER)
|
face = random.choice(self.KAWAII_BROWSER)
|
||||||
return f"{face} 👆 *click* {element} {time_str}"
|
return self._format_status("👆", f"clicked {element}", time_str, face)
|
||||||
|
|
||||||
elif tool_name == "browser_type":
|
elif tool_name == "browser_type":
|
||||||
text = args.get("text", "")
|
text = args.get("text", "")
|
||||||
if len(text) > 15:
|
if len(text) > 15:
|
||||||
text = text[:12] + "..."
|
text = text[:12] + "..."
|
||||||
face = random.choice(self.KAWAII_BROWSER)
|
face = random.choice(self.KAWAII_BROWSER)
|
||||||
return f"{face} ⌨️ typing '{text}' {time_str}"
|
return self._format_status("⌨️", f"typed '{text}'", time_str, face)
|
||||||
|
|
||||||
elif tool_name == "browser_scroll":
|
elif tool_name == "browser_scroll":
|
||||||
direction = args.get("direction", "down")
|
direction = args.get("direction", "down")
|
||||||
arrow = "↓" if direction == "down" else "↑"
|
arrow = "↓" if direction == "down" else "↑"
|
||||||
face = random.choice(self.KAWAII_BROWSER)
|
face = random.choice(self.KAWAII_BROWSER)
|
||||||
return f"{face} {arrow} scrolling {direction}... {time_str}"
|
return self._format_status(arrow, f"scrolled {direction}", time_str, face)
|
||||||
|
|
||||||
elif tool_name == "browser_back":
|
elif tool_name == "browser_back":
|
||||||
face = random.choice(self.KAWAII_BROWSER)
|
face = random.choice(self.KAWAII_BROWSER)
|
||||||
return f"{face} ← going back... {time_str}"
|
return self._format_status("←", "went back", time_str, face)
|
||||||
|
|
||||||
elif tool_name == "browser_vision":
|
elif tool_name == "browser_vision":
|
||||||
face = random.choice(self.KAWAII_BROWSER)
|
face = random.choice(self.KAWAII_BROWSER)
|
||||||
return f"{face} 👁️ analyzing visually... {time_str}"
|
return self._format_status("👁️", "analyzed visually", time_str, face)
|
||||||
|
|
||||||
# Image generation
|
# Image generation
|
||||||
elif tool_name == "image_generate":
|
elif tool_name == "image_generate":
|
||||||
@@ -456,37 +460,37 @@ class AIAgent:
|
|||||||
if len(prompt) > 20:
|
if len(prompt) > 20:
|
||||||
prompt = prompt[:17] + "..."
|
prompt = prompt[:17] + "..."
|
||||||
face = random.choice(self.KAWAII_CREATE)
|
face = random.choice(self.KAWAII_CREATE)
|
||||||
return f"{face} 🎨 creating '{prompt}'... {time_str}"
|
return self._format_status("🎨", f"created '{prompt}'", time_str, face)
|
||||||
|
|
||||||
# Skills - use large pool for variety
|
# Skills - use large pool for variety
|
||||||
elif tool_name == "skills_categories":
|
elif tool_name == "skills_categories":
|
||||||
face = random.choice(self.KAWAII_SKILL)
|
face = random.choice(self.KAWAII_SKILL)
|
||||||
return f"{face} 📚 listing categories... {time_str}"
|
return self._format_status("📚", "listed categories", time_str, face)
|
||||||
|
|
||||||
elif tool_name == "skills_list":
|
elif tool_name == "skills_list":
|
||||||
category = args.get("category", "skills")
|
category = args.get("category", "all")
|
||||||
face = random.choice(self.KAWAII_SKILL)
|
face = random.choice(self.KAWAII_SKILL)
|
||||||
return f"{face} 📋 listing {category} skills... {time_str}"
|
return self._format_status("📋", f"listed {category} skills", time_str, face)
|
||||||
|
|
||||||
elif tool_name == "skill_view":
|
elif tool_name == "skill_view":
|
||||||
name = args.get("name", "skill")
|
name = args.get("name", "skill")
|
||||||
face = random.choice(self.KAWAII_SKILL)
|
face = random.choice(self.KAWAII_SKILL)
|
||||||
return f"{face} 📖 loading {name}... {time_str}"
|
return self._format_status("📖", f"loaded {name}", time_str, face)
|
||||||
|
|
||||||
# Vision tools
|
# Vision tools
|
||||||
elif tool_name == "vision_analyze":
|
elif tool_name == "vision_analyze":
|
||||||
face = random.choice(self.KAWAII_BROWSER)
|
face = random.choice(self.KAWAII_BROWSER)
|
||||||
return f"{face} 👁️✨ analyzing image... {time_str}"
|
return self._format_status("👁️", "analyzed image", time_str, face)
|
||||||
|
|
||||||
# Mixture of agents
|
# Mixture of agents
|
||||||
elif tool_name == "mixture_of_agents":
|
elif tool_name == "mixture_of_agents":
|
||||||
face = random.choice(self.KAWAII_THINK)
|
face = random.choice(self.KAWAII_THINK)
|
||||||
return f"{face} 🧠💭 thinking REALLY hard... {time_str}"
|
return self._format_status("🧠", "deep reasoning done", time_str, face)
|
||||||
|
|
||||||
# Default fallback - random generic kawaii
|
# Default fallback - random generic kawaii
|
||||||
else:
|
else:
|
||||||
face = random.choice(self.KAWAII_GENERIC)
|
face = random.choice(self.KAWAII_GENERIC)
|
||||||
return f"{face} ⚡ {tool_name}... {time_str}"
|
return self._format_status("⚡", f"{tool_name} done", time_str, face)
|
||||||
|
|
||||||
def _has_content_after_think_block(self, content: str) -> bool:
|
def _has_content_after_think_block(self, content: str) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -839,7 +843,7 @@ class AIAgent:
|
|||||||
face = random.choice(KawaiiSpinner.KAWAII_THINKING)
|
face = random.choice(KawaiiSpinner.KAWAII_THINKING)
|
||||||
verb = random.choice(KawaiiSpinner.THINKING_VERBS)
|
verb = random.choice(KawaiiSpinner.THINKING_VERBS)
|
||||||
spinner_type = random.choice(['brain', 'sparkle', 'pulse', 'moon', 'star'])
|
spinner_type = random.choice(['brain', 'sparkle', 'pulse', 'moon', 'star'])
|
||||||
thinking_spinner = KawaiiSpinner(f"{face} {verb}...", spinner_type=spinner_type)
|
thinking_spinner = KawaiiSpinner(f"○ │ 🧠 {verb}... {face}", spinner_type=spinner_type)
|
||||||
thinking_spinner.start()
|
thinking_spinner.start()
|
||||||
|
|
||||||
# Log request details if verbose
|
# Log request details if verbose
|
||||||
@@ -897,7 +901,7 @@ class AIAgent:
|
|||||||
# Stop thinking spinner with cute completion message
|
# Stop thinking spinner with cute completion message
|
||||||
if thinking_spinner:
|
if thinking_spinner:
|
||||||
face = random.choice(["(◕‿◕✿)", "ヾ(^∇^)", "(≧◡≦)", "✧٩(ˊᗜˋ*)و✧", "(*^▽^*)"])
|
face = random.choice(["(◕‿◕✿)", "ヾ(^∇^)", "(≧◡≦)", "✧٩(ˊᗜˋ*)و✧", "(*^▽^*)"])
|
||||||
thinking_spinner.stop(f"{face} got it! ({api_duration:.1f}s)")
|
thinking_spinner.stop(f"● │ ✨ got it! ⏱ {api_duration:.1f}s {face}")
|
||||||
thinking_spinner = None
|
thinking_spinner = None
|
||||||
|
|
||||||
if not self.quiet_mode:
|
if not self.quiet_mode:
|
||||||
@@ -912,7 +916,7 @@ class AIAgent:
|
|||||||
if response is None or not hasattr(response, 'choices') or response.choices is None or len(response.choices) == 0:
|
if response is None or not hasattr(response, 'choices') or response.choices is None or len(response.choices) == 0:
|
||||||
# Stop spinner before printing error messages
|
# Stop spinner before printing error messages
|
||||||
if thinking_spinner:
|
if thinking_spinner:
|
||||||
thinking_spinner.stop(f"(´;ω;`) oops, retrying...")
|
thinking_spinner.stop(f"● │ ⚠️ oops, retrying... (´;ω;`)")
|
||||||
thinking_spinner = None
|
thinking_spinner = None
|
||||||
|
|
||||||
# This is often rate limiting or provider returning malformed response
|
# This is often rate limiting or provider returning malformed response
|
||||||
@@ -1021,7 +1025,7 @@ class AIAgent:
|
|||||||
except Exception as api_error:
|
except Exception as api_error:
|
||||||
# Stop spinner before printing error messages
|
# Stop spinner before printing error messages
|
||||||
if thinking_spinner:
|
if thinking_spinner:
|
||||||
thinking_spinner.stop(f"(╥_╥) error, retrying...")
|
thinking_spinner.stop(f"● │ ❌ error, retrying... (╥_╥)")
|
||||||
thinking_spinner = None
|
thinking_spinner = None
|
||||||
|
|
||||||
retry_count += 1
|
retry_count += 1
|
||||||
@@ -1121,10 +1125,16 @@ class AIAgent:
|
|||||||
self._invalid_tool_retries = 0
|
self._invalid_tool_retries = 0
|
||||||
|
|
||||||
# Validate tool call arguments are valid JSON
|
# Validate tool call arguments are valid JSON
|
||||||
|
# Also fix empty strings to be empty objects (common model quirk)
|
||||||
invalid_json_args = []
|
invalid_json_args = []
|
||||||
for tc in assistant_message.tool_calls:
|
for tc in assistant_message.tool_calls:
|
||||||
|
args_str = tc.function.arguments
|
||||||
|
# Treat empty/whitespace strings as empty object
|
||||||
|
if not args_str or not args_str.strip():
|
||||||
|
tc.function.arguments = "{}"
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
json.loads(tc.function.arguments)
|
json.loads(args_str)
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
invalid_json_args.append((tc.function.name, str(e)))
|
invalid_json_args.append((tc.function.name, str(e)))
|
||||||
|
|
||||||
@@ -1228,7 +1238,40 @@ class AIAgent:
|
|||||||
spinner_type, tool_emojis = tool_spinners.get(function_name, ('dots', ['⚙️', '🔧', '⚡', '✨']))
|
spinner_type, tool_emojis = tool_spinners.get(function_name, ('dots', ['⚙️', '🔧', '⚡', '✨']))
|
||||||
face = random.choice(KawaiiSpinner.KAWAII_WAITING)
|
face = random.choice(KawaiiSpinner.KAWAII_WAITING)
|
||||||
tool_emoji = random.choice(tool_emojis)
|
tool_emoji = random.choice(tool_emojis)
|
||||||
spinner = KawaiiSpinner(f"{face} {tool_emoji} {function_name}...", spinner_type=spinner_type)
|
|
||||||
|
# Build descriptive spinner message based on tool type
|
||||||
|
# Format: ○ │ emoji action details... face
|
||||||
|
if function_name == 'terminal':
|
||||||
|
cmd = function_args.get('command', '')
|
||||||
|
cmd_preview = cmd[:40] + '...' if len(cmd) > 40 else cmd
|
||||||
|
action = f"{tool_emoji} $ {cmd_preview}"
|
||||||
|
elif function_name == 'web_search':
|
||||||
|
query = function_args.get('query', '')[:35]
|
||||||
|
action = f"{tool_emoji} searching: {query}"
|
||||||
|
elif function_name == 'web_extract':
|
||||||
|
url = function_args.get('url', '')
|
||||||
|
domain = url.split('//')[-1].split('/')[0][:25] if '//' in url else url[:25]
|
||||||
|
action = f"{tool_emoji} reading: {domain}"
|
||||||
|
elif function_name == 'skill_view':
|
||||||
|
skill = function_args.get('name', 'skill')
|
||||||
|
action = f"{tool_emoji} loading: {skill}"
|
||||||
|
elif function_name == 'skills_list':
|
||||||
|
cat = function_args.get('category', 'all')
|
||||||
|
action = f"{tool_emoji} listing: {cat} skills"
|
||||||
|
elif function_name == 'browser_navigate':
|
||||||
|
url = function_args.get('url', '')
|
||||||
|
domain = url.split('//')[-1].split('/')[0][:25] if '//' in url else url[:25]
|
||||||
|
action = f"{tool_emoji} navigating: {domain}"
|
||||||
|
elif function_name == 'image_generate':
|
||||||
|
prompt = function_args.get('prompt', '')[:30]
|
||||||
|
action = f"{tool_emoji} creating: {prompt}..."
|
||||||
|
else:
|
||||||
|
action = f"{tool_emoji} {function_name}..."
|
||||||
|
|
||||||
|
# Aligned format with fixed prefix
|
||||||
|
spinner_msg = f"○ │ {action} {face}"
|
||||||
|
|
||||||
|
spinner = KawaiiSpinner(spinner_msg, spinner_type=spinner_type)
|
||||||
spinner.start()
|
spinner.start()
|
||||||
try:
|
try:
|
||||||
function_result = handle_function_call(function_name, function_args, effective_task_id)
|
function_result = handle_function_call(function_name, function_args, effective_task_id)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user