ReActCore
introduces three checkpoints during streaming: content_ended
(after text content), before_tool_calls
(before tool calls), and after_tool_calls
(after tool calls), enabling precise interception and state synchronization of the execution flow.MessageQueue
class in run.py
, supporting async enqueue, drain, and remove operations. Users can now queue messages while the LLM is running; queued messages are sent automatically after the current task completes. The frontend introduces a QueueBar
component to display queued messages, with CSS spinning animation, single-line ellipsis, and hover-to-delete functionality.MessageQueue.drain_all()
now merges consecutive messages with the same name
into a single message, preventing fragmented user input when multiple queue entries share the same sender.message_queued
, queue_drained
, and queue_returned
(useRunWebSocket.ts
). The frontend processes queue state updates in real time.queue_returned
. Checkpoint stops cleanly clear the queue and automatically start the next message.SystemMessage
type (with notification
role) to separate error messages from assistant content. Errors are now rendered as independent notification bubbles, no longer embedded within assistant message cards.ReActCore
initializes a tiktoken
encoder on startup for real-time token counting during streaming. Unknown models fall back to o200k_base
.ModelManager
has been upgraded from Select
to AutoComplete
, allowing users to type custom model names not in the predefined list.ToolCallsBlock
type (message_block.py
), unifying the internal tool-call format to an OpenAI-style tool_calls
list, replacing the legacy ToolUseBlock
._get_valid_token_value
to uniformly handle None
/0/empty values from the API, preventing placeholder values from overwriting accumulated values.react_core.py
refactored: each ChatResponse
now contains only a single block type (SoloTextBlock
or SoloThinkingBlock
), replacing the previous pattern of accumulating multiple blocks before yielding. This enables the frontend to precisely handle each type of incremental content.LLMMessage[]
to Message[]
(LLMMessage | SystemMessage
). convertToLLMMessages
now splits system messages such as error
into independent SystemMessage
entries rather than reusing LLMMessage
. Updated MessageList
and RunPanel
to render SystemMessage
entries.save_assistant_message
now follow the same code path. error
is saved as an independent field and no longer written into the data block.on_execution_done
now detects empty-collector scenarios (where stream_callback
was never triggered) and automatically sets the status to error
with detailed LLM failure output. This prevents false "completed" status during silent failures.createNewSession
in runPanelStore
now immediately inserts the new session into the sessions
array, ensuring the UI reflects the new session without requiring a refresh.isRunning
or isWaitingReply
is true. The ENTER key now triggers send (queue) as long as the input has content, no longer blocked by isRunning
state.execution_start
now generates a new msg_asst_${Date.now()}
ID, preventing React key conflicts when the queue drain triggers a new assistant message that would otherwise duplicate the previous message ID.websocket_handler.py
has been significantly simplified, removing complex grace period and takeover logic. RunContext
now owns its own event loop, and the WebSocket only acts as a transport layer with injected callbacks.aclose()
closing the stream in ReActCore
no longer raises CancelledError
; it is treated as a normal end (breaking out of the loop), avoiding misinterpreting expected stream closure as a cancellation error.ToolCallEventManager
now uniformly sends tool-call events to the frontend through tool_calls
blocks, removing the legacy tool_use
block handling logic.TruncatedFormatterBase
removed token_counter
and max_tokens
parameters along with dead code such as _truncate
and _count
; the constructor is simplified to a no-arg empty implementation. OpenAIChatFormatter
removed the OpenAIMultiAgentFormatter
subclass.anthropic_model.py
captures input_tokens
in the message_start
event and accumulated output_tokens
in the message_delta
event, resolving inaccurate token counts in Anthropic streaming responses._convert_openai_to_anthropic_messages
static method that converts OpenAI-format messages (tool_calls
, reasoning_content
) to Anthropic format (tool_use
, thinking
).anthropic_model.py
and qwen_model.py
now yield a separate ChatResponse
for each tool_call
, enabling precise handling on the frontend.backend/SoloAgent/token_counter/
directory, including __init__.py
, openai_token_counter.py
, and token_base.py
. The original functionality has been merged into ReActCore
inline accumulation.backend/SoloAgent/formatter/truncated_formatter_base.py
— removed from the exports in __init__.py
; the file is preserved but is no longer public API.OpenAIMultiAgentFormatter
from openai_formatter.py
.ToolUseBlock
— the message block type was unified from tool_use
to tool_calls
; the legacy tool_use
type is no longer used.`_convert_anthropic_message_to_solo_format`
function — removed a 99-line Anthropic→Solo format conversion function in `anthropic_model.py`
, replaced by the new OpenAI→Anthropic conversion method.reasoning_content
from content in OpenAIChatFormatter
, and removed the compatibility code (with DEBUG logs) that forcibly added an empty reasoning_content
to messages carrying tool_calls
.TruncatedFormatterBase._group_messages
now only checks tool_calls
and tool_result
types, removing tool_use
compatibility code.agent.reply
caught an exception and returned an error string, but _execute_agent
treated the run as completed
because the collector was empty. on_execution_done
now checks collector.get_chunk_count() > 0
and forces status="error"
with the actual LLM error output.content
blocks; they are rendered as independent notification
-role messages with proper styling and status indicators.tool_use
blocks in streaming responses, unifying to the tool_calls
format.OpenAIChatFormatter
adds filtering logic: when an assistant message has empty content
and no tool_calls
, it is skipped. This fixes the LLM API error "Input is a zero-length, empty document".ChunkCollector._extract_raw_type
in run.py
removed recognition of the 'tool_use'
type string, unifying the mapping to `'tool_calls'`
.`aclose()`
closing the stream in react_core.py
no longer raises CancelledError
; it breaks out of the loop and is treated as a normal end, avoiding misinterpreting expected stream closure as a cancellation error.aclose
in react_core.py
, it is no longer converted to asyncio.CancelledError
; it breaks out of the loop and is treated as a normal end, avoiding misinterpreting expected stream closure as a cancellation error.flow_compiler.py
adds new logic: immediately after creating the flow, check whether cancel_event
is already set, and if so, cancel the flow right away, avoiding wasted HTTP connections.flow_compiler.py
fixed a bug where an interrupted Agent incorrectly returned "completed"
; it now returns "stop"
status. The output
field is no longer filled with error
, preventing error pollution of the output.MessageList.tsx
unifies on the W3C Clipboard API, ensuring that Windows clipboard history (Win+V) is correctly captured; fixed the issue where clipboard history could not be triggered on Windows.We're looking for like-minded contributors who share our passion for SoloEngine and Agentic AI. Every contribution — from a typo fix to a full feature — makes SoloEngine better.
📝 Contributing Guide · 💬 Discussions · 📧 Contact Us