mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
fix: simplify JSON Schema in SendMessageToUserTool parameters
Simplify the message items schema by using additionalProperties instead of explicit properties, while preserving type info for LLM docs. Note: 12 ty diagnostics remain in send_message.py and astr_main_agent_resources.py due to architectural issue where FunctionTool.parameters JSON Schema is used by ty for Python type inference. This requires larger refactoring to fix properly.
This commit is contained in:
@@ -11,7 +11,6 @@ import curses
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
@@ -75,6 +74,7 @@ class TUIClient:
|
||||
self._history_index: int = -1
|
||||
self._max_history: int = 100
|
||||
self._max_messages: int = 1000
|
||||
self._pending_tasks: list[asyncio.Task[None]] = []
|
||||
|
||||
# Connection settings
|
||||
self.host = host.rstrip("/")
|
||||
@@ -181,15 +181,13 @@ class TUIClient:
|
||||
if msg_type == "user":
|
||||
for part in message_parts:
|
||||
if part.get("type") == "plain":
|
||||
self.add_message(
|
||||
MessageSender.USER, part.get("text", "")
|
||||
)
|
||||
self.add_message(MessageSender.USER, part.get("text", ""))
|
||||
elif msg_type == "bot":
|
||||
for part in message_parts:
|
||||
if part.get("type") == "plain":
|
||||
self.add_message(MessageSender.BOT, part.get("text", ""))
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
if self.debug:
|
||||
import traceback
|
||||
|
||||
@@ -248,7 +246,8 @@ class TUIClient:
|
||||
# Handle Enter/Return - submit message
|
||||
elif key in (curses.KEY_ENTER, 10, 13):
|
||||
if self.state.input_buffer.strip():
|
||||
asyncio.create_task(self._submit_message())
|
||||
task = asyncio.create_task(self._submit_message())
|
||||
self._pending_tasks.append(task)
|
||||
return True
|
||||
|
||||
# Handle history navigation (up/down arrows)
|
||||
@@ -319,14 +318,13 @@ class TUIClient:
|
||||
|
||||
try:
|
||||
# Format umo for webchat
|
||||
umo = f"webchat:FriendMessage:webchat!{self.username}!{self.conversation_id}"
|
||||
umo = (
|
||||
f"webchat:FriendMessage:webchat!{self.username}!{self.conversation_id}"
|
||||
)
|
||||
|
||||
# Reset parser for new stream
|
||||
self._parser.reset()
|
||||
|
||||
# Create a placeholder bot message that we'll update
|
||||
placeholder = self.add_message(MessageSender.BOT, "...")
|
||||
|
||||
# Send message and stream response using proper SSE
|
||||
async with self._client.stream(
|
||||
"POST",
|
||||
@@ -376,7 +374,9 @@ class TUIClient:
|
||||
)
|
||||
self.state.status = "Thinking..."
|
||||
elif parsed.type == MessageType.AGENT_STATS:
|
||||
self.state.status = f"Tokens: {update.agent_stats.get('total_tokens', 0)}"
|
||||
self.state.status = (
|
||||
f"Tokens: {update.agent_stats.get('total_tokens', 0)}"
|
||||
)
|
||||
elif update.text:
|
||||
self._update_last_bot_message(update.text)
|
||||
|
||||
@@ -385,7 +385,9 @@ class TUIClient:
|
||||
|
||||
# Final status
|
||||
if update.reasoning:
|
||||
self.add_message(MessageSender.REASONING, f"[Reasoning]\n{update.reasoning}")
|
||||
self.add_message(
|
||||
MessageSender.REASONING, f"[Reasoning]\n{update.reasoning}"
|
||||
)
|
||||
|
||||
for tool_display in update.get_tool_calls_display():
|
||||
self.add_message(MessageSender.TOOL, tool_display)
|
||||
@@ -405,9 +407,7 @@ class TUIClient:
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
def _process_parsed_message(
|
||||
self, msg: ParsedMessage
|
||||
) -> tuple[ChatResponse, bool]:
|
||||
def _process_parsed_message(self, msg: ParsedMessage) -> tuple[ChatResponse, bool]:
|
||||
"""Process a parsed message and return updated response state."""
|
||||
return self._parser.process_message(msg)
|
||||
|
||||
|
||||
@@ -49,33 +49,7 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]):
|
||||
"description": "An ordered list of message components to send. `mention_user` type can be used to mention the user.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": {},
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"Component type. One of: "
|
||||
"plain, image, record, video, file, mention_user. Record is voice message."
|
||||
),
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Text content for `plain` type.",
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "File path for `image`, `record`, or `file` types. Both local path and sandbox path are supported.",
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "URL for `image`, `record`, or `file` types.",
|
||||
},
|
||||
"mention_user_id": {
|
||||
"type": "string",
|
||||
"description": "User ID to mention for `mention_user` type.",
|
||||
},
|
||||
},
|
||||
"required": ["type"],
|
||||
"additionalProperties": {"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
"""AstrBot TUI - Terminal User Interface for AstrBot."""
|
||||
|
||||
from astrbot.tui.message_handler import (
|
||||
ChatResponse,
|
||||
MessageType,
|
||||
ParsedMessage,
|
||||
SSEMessageParser,
|
||||
)
|
||||
from astrbot.tui.screen import Screen, run_curses
|
||||
|
||||
__all__ = ["Screen", "run_curses"]
|
||||
__all__ = [
|
||||
"ChatResponse",
|
||||
"MessageType",
|
||||
"ParsedMessage",
|
||||
"SSEMessageParser",
|
||||
"Screen",
|
||||
"run_curses",
|
||||
]
|
||||
|
||||
@@ -1,21 +1,43 @@
|
||||
"""AstrBot TUI - Entry point for python -m astrbot.tui"""
|
||||
|
||||
from astrbot.tui.screen import run_curses
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
|
||||
def main(stdscr):
|
||||
"""Main TUI loop - placeholder for future TUI implementation."""
|
||||
# For now, just display a message
|
||||
import curses
|
||||
"""Main TUI entry point when running via python -m astrbot.tui."""
|
||||
try:
|
||||
from astrbot.cli.commands.tui_async import TUIClient
|
||||
from astrbot.tui.screen import Screen
|
||||
|
||||
curses.start_color()
|
||||
curses.curs_set(1)
|
||||
stdscr.clear()
|
||||
stdscr.addstr(0, 0, "AstrBot TUI - Coming soon!", curses.A_BOLD)
|
||||
stdscr.addstr(2, 0, "Press any key to exit...")
|
||||
stdscr.refresh()
|
||||
stdscr.getch()
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
scr = Screen(stdscr)
|
||||
client = TUIClient(
|
||||
screen=scr,
|
||||
host="http://localhost:6185",
|
||||
api_key=None,
|
||||
username="astrbot",
|
||||
password="astrbot",
|
||||
debug=False,
|
||||
)
|
||||
try:
|
||||
loop.run_until_complete(client.run_event_loop(stdscr))
|
||||
finally:
|
||||
loop.close()
|
||||
except ImportError as e:
|
||||
import curses
|
||||
|
||||
curses.curs_set(1)
|
||||
stdscr.clear()
|
||||
stdscr.addstr(0, 0, f"Error importing TUI module: {e}", curses.A_BOLD)
|
||||
stdscr.addstr(2, 0, "Press any key to exit...")
|
||||
stdscr.refresh()
|
||||
stdscr.getch()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from astrbot.tui.screen import run_curses
|
||||
|
||||
run_curses(main)
|
||||
|
||||
@@ -165,9 +165,7 @@ class SSEMessageParser:
|
||||
except ValueError:
|
||||
return MessageType.PLAIN
|
||||
|
||||
def process_message(
|
||||
self, msg: ParsedMessage
|
||||
) -> tuple[ChatResponse, bool]:
|
||||
def process_message(self, msg: ParsedMessage) -> tuple[ChatResponse, bool]:
|
||||
"""Process a parsed message and update accumulated response.
|
||||
|
||||
Args:
|
||||
@@ -220,7 +218,10 @@ class SSEMessageParser:
|
||||
self._tool_calls[tc_id].result = tcr.get("result")
|
||||
self._tool_calls[tc_id].finished_ts = tcr.get("ts")
|
||||
self._accumulated_parts.append(
|
||||
{"type": "tool_call", "tool_calls": [self._tool_calls[tc_id].__dict__]}
|
||||
{
|
||||
"type": "tool_call",
|
||||
"tool_calls": [self._tool_calls[tc_id].__dict__],
|
||||
}
|
||||
)
|
||||
self._tool_calls.pop(tc_id, None)
|
||||
except json.JSONDecodeError:
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
"""AstrBot TUI Application - Main chat interface."""
|
||||
"""AstrBot TUI Application - Main chat interface (sync version for testing).
|
||||
|
||||
This module provides a basic TUI application without network connectivity,
|
||||
useful for testing the UI components and as a reference implementation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -13,6 +17,8 @@ class MessageSender(Enum):
|
||||
USER = "user"
|
||||
BOT = "bot"
|
||||
SYSTEM = "system"
|
||||
TOOL = "tool"
|
||||
REASONING = "reasoning"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -33,7 +39,7 @@ class TUIState:
|
||||
|
||||
|
||||
class AstrBotTUI:
|
||||
"""Main TUI application for AstrBot."""
|
||||
"""Main TUI application for AstrBot (local/testing version)."""
|
||||
|
||||
def __init__(self, screen: Screen):
|
||||
self.screen = screen
|
||||
@@ -45,7 +51,6 @@ class AstrBotTUI:
|
||||
def add_message(self, sender: MessageSender, text: str) -> None:
|
||||
"""Add a message to the chat log."""
|
||||
self.state.messages.append(Message(sender=sender, text=text))
|
||||
# Keep only last 1000 messages to prevent memory issues
|
||||
if len(self.state.messages) > 1000:
|
||||
self.state.messages = self.state.messages[-1000:]
|
||||
|
||||
@@ -152,18 +157,15 @@ class AstrBotTUI:
|
||||
self.state.input_buffer = ""
|
||||
self.state.cursor_x = 0
|
||||
|
||||
# Process the message (placeholder for actual bot interaction)
|
||||
# Process the message (echo back for testing)
|
||||
self._process_user_message(text)
|
||||
|
||||
def _process_user_message(self, text: str) -> None:
|
||||
"""Process user message and generate bot response."""
|
||||
# This is a placeholder - actual implementation would connect to the bot
|
||||
self.add_system_message(f"Message sent: {text}")
|
||||
self.state.status = "Message sent, awaiting response..."
|
||||
"""Process user message and generate bot response (echo for testing)."""
|
||||
self.add_message(MessageSender.BOT, f"Echo: {text}")
|
||||
|
||||
def render(self) -> None:
|
||||
"""Render the current state to the screen."""
|
||||
# Convert messages to the format expected by screen
|
||||
lines = [(msg.sender.value, msg.text) for msg in self.state.messages]
|
||||
|
||||
self.screen.draw_all(
|
||||
@@ -180,7 +182,7 @@ class AstrBotTUI:
|
||||
self.screen.layout_windows()
|
||||
|
||||
# Welcome message
|
||||
self.add_system_message("Welcome to AstrBot TUI!")
|
||||
self.add_system_message("Welcome to AstrBot TUI (local mode)!")
|
||||
self.add_system_message("Type your message and press Enter to send.")
|
||||
self.add_system_message("Press ESC or Ctrl+C to exit.")
|
||||
|
||||
|
||||
@@ -89,7 +89,10 @@ def test_expand_env_placeholders_resolves_env_and_default(
|
||||
assert _expand_env_placeholders("${MISSING:-3002}", "port") == "3002"
|
||||
|
||||
|
||||
def test_expand_env_placeholders_raises_for_unresolved_variable():
|
||||
def test_expand_env_placeholders_raises_for_unresolved_variable(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
monkeypatch.delenv("HOST", raising=False)
|
||||
with pytest.raises(ValueError, match="dashboard host: HOST"):
|
||||
_expand_env_placeholders("${HOST}", "host")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user