Compare commits
No commits in common. "3630e62e185e6b6407b45f4b1768222e4ef5a097" and "fc7df67474fa7eeda0130a2dca877e4732adc392" have entirely different histories.
3630e62e18
...
fc7df67474
@ -11,7 +11,6 @@ use crate::agent::{AgentLoop, AgentRuntimeConfig, EmittedMessageHandler, Persist
|
|||||||
use crate::bus::ChatMessage;
|
use crate::bus::ChatMessage;
|
||||||
use crate::bus::message::{OutboundMessage, OutboundEventKind};
|
use crate::bus::message::{OutboundMessage, OutboundEventKind};
|
||||||
use crate::bus::MessageBus;
|
use crate::bus::MessageBus;
|
||||||
use crate::providers::StreamDelta;
|
|
||||||
use crate::config::{LLMProviderConfig, SubagentsConfig};
|
use crate::config::{LLMProviderConfig, SubagentsConfig};
|
||||||
use crate::storage::{ConversationRepository, SessionStore};
|
use crate::storage::{ConversationRepository, SessionStore};
|
||||||
use crate::tools::{ToolContext, ToolRegistry};
|
use crate::tools::{ToolContext, ToolRegistry};
|
||||||
@ -108,7 +107,6 @@ struct SubAgentEmitter {
|
|||||||
metadata: HashMap<String, String>,
|
metadata: HashMap<String, String>,
|
||||||
store: Arc<SessionStore>,
|
store: Arc<SessionStore>,
|
||||||
sub_session_id: String,
|
sub_session_id: String,
|
||||||
stream_message_id: std::sync::Mutex<Option<String>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -161,41 +159,6 @@ impl EmittedMessageHandler for SubAgentEmitter {
|
|||||||
self.persist_todo_write_result(&message);
|
self.persist_todo_write_result(&message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_stream_delta(&self, delta: &StreamDelta) {
|
|
||||||
let message_id = {
|
|
||||||
let mut guard = self.stream_message_id.lock().unwrap();
|
|
||||||
guard.get_or_insert_with(|| uuid::Uuid::new_v4().to_string()).clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let outbound = if delta.content.is_empty() && delta.reasoning_content.is_none() {
|
|
||||||
OutboundMessage::stream_end(
|
|
||||||
&self.channel_name,
|
|
||||||
&self.chat_id,
|
|
||||||
None,
|
|
||||||
&message_id,
|
|
||||||
self.metadata.clone(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
OutboundMessage::stream_delta(
|
|
||||||
&self.channel_name,
|
|
||||||
&self.chat_id,
|
|
||||||
None,
|
|
||||||
&message_id,
|
|
||||||
&delta.content,
|
|
||||||
delta.reasoning_content.clone(),
|
|
||||||
self.metadata.clone(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(error) = self.bus.publish_outbound(outbound).await {
|
|
||||||
tracing::error!(error = %error, channel = %self.channel_name, "Failed to publish sub-agent stream delta");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_stream_message_id(&self, id: &str) {
|
|
||||||
*self.stream_message_id.lock().unwrap() = Some(id.to_string());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubAgentEmitter {
|
impl SubAgentEmitter {
|
||||||
@ -353,7 +316,6 @@ impl DefaultSubAgentRuntime {
|
|||||||
metadata,
|
metadata,
|
||||||
store: self.store.clone(),
|
store: self.store.clone(),
|
||||||
sub_session_id: session.session_id.clone(),
|
sub_session_id: session.session_id.clone(),
|
||||||
stream_message_id: std::sync::Mutex::new(None),
|
|
||||||
},
|
},
|
||||||
self.conversation_repository.clone(),
|
self.conversation_repository.clone(),
|
||||||
session.session_id.clone(),
|
session.session_id.clone(),
|
||||||
|
|||||||
@ -242,7 +242,7 @@ function ThinkingSection({ content }: { content: string }) {
|
|||||||
const [expanded, setExpanded] = useState(false)
|
const [expanded, setExpanded] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-[var(--border-color)] bg-[var(--overlay-hover)] overflow-hidden">
|
<div className="mb-3 rounded-lg border border-[var(--border-color)] bg-[var(--overlay-hover)] overflow-hidden">
|
||||||
<button
|
<button
|
||||||
onClick={(e) => { e.stopPropagation(); setExpanded(!expanded) }}
|
onClick={(e) => { e.stopPropagation(); setExpanded(!expanded) }}
|
||||||
className="flex items-center gap-2 w-full px-3 py-2 text-xs text-[var(--text-muted)] hover:text-[var(--text-secondary)] hover:bg-[var(--overlay-subtle)] transition-colors cursor-pointer select-none"
|
className="flex items-center gap-2 w-full px-3 py-2 text-xs text-[var(--text-muted)] hover:text-[var(--text-secondary)] hover:bg-[var(--overlay-subtle)] transition-colors cursor-pointer select-none"
|
||||||
@ -449,7 +449,7 @@ export function MessageBubble({ message, onNavigateToSubAgent, showThinking = tr
|
|||||||
|
|
||||||
{/* 模型思考内容(工具调用时展示) */}
|
{/* 模型思考内容(工具调用时展示) */}
|
||||||
{showThinking && message.reasoningContent && !toolExpanded && (
|
{showThinking && message.reasoningContent && !toolExpanded && (
|
||||||
<div className="px-3 pb-1 mb-2">
|
<div className="px-3 pb-1">
|
||||||
<ThinkingSection content={message.reasoningContent} />
|
<ThinkingSection content={message.reasoningContent} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -513,9 +513,7 @@ export function MessageBubble({ message, onNavigateToSubAgent, showThinking = tr
|
|||||||
<div className="border-t border-[var(--border-color)] px-3 py-2 space-y-2">
|
<div className="border-t border-[var(--border-color)] px-3 py-2 space-y-2">
|
||||||
{/* 模型思考内容(展开时也展示) */}
|
{/* 模型思考内容(展开时也展示) */}
|
||||||
{showThinking && message.reasoningContent && (
|
{showThinking && message.reasoningContent && (
|
||||||
<div className="mb-2">
|
<ThinkingSection content={message.reasoningContent} />
|
||||||
<ThinkingSection content={message.reasoningContent} />
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{taskResult ? (
|
{taskResult ? (
|
||||||
<>
|
<>
|
||||||
@ -683,12 +681,9 @@ export function MessageBubble({ message, onNavigateToSubAgent, showThinking = tr
|
|||||||
// 模型思考内容(仅助手消息,非工具消息)
|
// 模型思考内容(仅助手消息,非工具消息)
|
||||||
<>
|
<>
|
||||||
{showThinking && !isTool && message.reasoningContent && (
|
{showThinking && !isTool && message.reasoningContent && (
|
||||||
<div className={message.content.trim() ? 'mb-3' : ''}>
|
<ThinkingSection content={message.reasoningContent} />
|
||||||
<ThinkingSection content={message.reasoningContent} />
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{/* AI 和工具消息使用 Markdown 渲染 */}
|
{/* AI 和工具消息使用 Markdown 渲染 */}
|
||||||
{message.content.trim() && (
|
|
||||||
<div className="markdown-content text-sm leading-relaxed">
|
<div className="markdown-content text-sm leading-relaxed">
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
@ -768,7 +763,6 @@ export function MessageBubble({ message, onNavigateToSubAgent, showThinking = tr
|
|||||||
{message.content}
|
{message.content}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</>)}
|
</>)}
|
||||||
{message.attachments && message.attachments.length > 0 && (
|
{message.attachments && message.attachments.length > 0 && (
|
||||||
<div className="mt-2 space-y-2">
|
<div className="mt-2 space-y-2">
|
||||||
|
|||||||
@ -28,7 +28,6 @@ import type {
|
|||||||
Channel,
|
Channel,
|
||||||
ChannelList,
|
ChannelList,
|
||||||
StreamDelta,
|
StreamDelta,
|
||||||
StreamEnd,
|
|
||||||
} from '../types/protocol'
|
} from '../types/protocol'
|
||||||
|
|
||||||
// 简化后的层级状态
|
// 简化后的层级状态
|
||||||
@ -188,9 +187,6 @@ export function useChat(): UseChatReturn {
|
|||||||
|| message.type === 'tool_pending' || message.type === 'assistant_response') {
|
|| message.type === 'tool_pending' || message.type === 'assistant_response') {
|
||||||
return (message as ToolCall | ToolResult | ToolPending | AssistantResponse).subagent_task_id
|
return (message as ToolCall | ToolResult | ToolPending | AssistantResponse).subagent_task_id
|
||||||
}
|
}
|
||||||
if (message.type === 'stream_delta' || message.type === 'stream_end') {
|
|
||||||
return (message as StreamDelta | StreamEnd).subagent_task_id
|
|
||||||
}
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,18 +258,6 @@ export function useChat(): UseChatReturn {
|
|||||||
subagentTaskId: msg.subagent_task_id,
|
subagentTaskId: msg.subagent_task_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'stream_delta': {
|
|
||||||
const msg = message as StreamDelta
|
|
||||||
return {
|
|
||||||
id: msg.id,
|
|
||||||
role: 'assistant' as const,
|
|
||||||
content: msg.delta,
|
|
||||||
timestamp: Math.floor(Date.now() / 1000),
|
|
||||||
type: 'message' as const,
|
|
||||||
subagentTaskId: msg.subagent_task_id,
|
|
||||||
reasoningContent: msg.reasoning_delta,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'error': {
|
case 'error': {
|
||||||
return {
|
return {
|
||||||
id: generateMessageId(),
|
id: generateMessageId(),
|
||||||
@ -288,48 +272,15 @@ export function useChat(): UseChatReturn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append a server message to the sub-agent view (with streaming delta accumulation)
|
// Append a server message to the sub-agent view
|
||||||
const appendToSubAgentViewMessage = (message: WsOutbound) => {
|
const appendToSubAgentViewMessage = (message: WsOutbound) => {
|
||||||
// stream_delta: accumulate into existing message by ID, or create new
|
|
||||||
if (message.type === 'stream_delta') {
|
|
||||||
const msg = message as StreamDelta
|
|
||||||
setSubAgentView((prev) => {
|
|
||||||
if (!prev) return prev
|
|
||||||
const existingIdx = prev.messages.findIndex(m => m.id === msg.id && m.type === 'message')
|
|
||||||
if (existingIdx >= 0) {
|
|
||||||
const updated = [...prev.messages]
|
|
||||||
const existing = updated[existingIdx]
|
|
||||||
updated[existingIdx] = {
|
|
||||||
...existing,
|
|
||||||
content: existing.content + msg.delta,
|
|
||||||
reasoningContent: msg.reasoning_delta
|
|
||||||
? (existing.reasoningContent || '') + msg.reasoning_delta
|
|
||||||
: existing.reasoningContent,
|
|
||||||
}
|
|
||||||
return { ...prev, messages: updated }
|
|
||||||
}
|
|
||||||
const chatMsg = serverMessageToChatMessage(message)
|
|
||||||
return chatMsg ? { ...prev, messages: [...prev.messages, chatMsg] } : prev
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// stream_end: no-op, assistant_response will replace
|
|
||||||
if (message.type === 'stream_end') return
|
|
||||||
// Other messages: assistant_response replaces streamed message by ID
|
|
||||||
const chatMsg = serverMessageToChatMessage(message)
|
const chatMsg = serverMessageToChatMessage(message)
|
||||||
if (chatMsg) {
|
if (chatMsg) {
|
||||||
setSubAgentView((prev) => {
|
setSubAgentView((prev) =>
|
||||||
if (!prev) return prev
|
prev
|
||||||
if (message.type === 'assistant_response') {
|
? { ...prev, messages: [...prev.messages, chatMsg] }
|
||||||
const existingIdx = prev.messages.findIndex(m => m.id === chatMsg.id && m.type === 'message')
|
: prev
|
||||||
if (existingIdx >= 0) {
|
)
|
||||||
const updated = [...prev.messages]
|
|
||||||
updated[existingIdx] = chatMsg
|
|
||||||
return { ...prev, messages: updated }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { ...prev, messages: [...prev.messages, chatMsg] }
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user