feat(streaming): add Tauri streaming chat command
Add agent_chat_stream Tauri command that:
- Accepts StreamChatRequest with agent_id, session_id, message
- Gets streaming receiver from kernel.send_message_stream()
- Spawns background task to emit Tauri events ("stream:chunk")
- Emits StreamChatEvent types (Delta, ToolStart, ToolEnd, Complete, Error)
- Includes session_id for frontend routing
Registered in lib.rs invoke_handler.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
76
Cargo.lock
generated
76
Cargo.lock
generated
@@ -101,6 +101,15 @@ version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
@@ -215,6 +224,28 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
|
||||
dependencies = [
|
||||
"async-stream-impl",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream-impl"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.7.1"
|
||||
@@ -831,6 +862,17 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.20"
|
||||
@@ -6805,9 +6847,12 @@ dependencies = [
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"zclaw-hands",
|
||||
"zclaw-memory",
|
||||
"zclaw-protocols",
|
||||
"zclaw-runtime",
|
||||
"zclaw-types",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6837,6 +6882,7 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"zclaw-types",
|
||||
]
|
||||
|
||||
@@ -6844,6 +6890,7 @@ dependencies = [
|
||||
name = "zclaw-runtime"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"futures",
|
||||
@@ -6966,12 +7013,41 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"displaydoc",
|
||||
"flate2",
|
||||
"indexmap 2.13.0",
|
||||
"memchr",
|
||||
"thiserror 2.0.18",
|
||||
"zopfli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"log",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.10.0"
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
//! eliminating the need for external OpenFang process.
|
||||
|
||||
use std::sync::Arc;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tauri::{AppHandle, Emitter, Manager, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::Mutex;
|
||||
use futures::StreamExt;
|
||||
use zclaw_kernel::Kernel;
|
||||
use zclaw_types::{AgentConfig, AgentId, AgentInfo, AgentState};
|
||||
|
||||
@@ -342,6 +343,102 @@ pub async fn agent_chat(
|
||||
})
|
||||
}
|
||||
|
||||
/// Streaming chat event for Tauri emission
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
pub enum StreamChatEvent {
|
||||
/// Text delta received
|
||||
Delta { delta: String },
|
||||
/// Tool use started
|
||||
ToolStart { name: String, input: serde_json::Value },
|
||||
/// Tool use completed
|
||||
ToolEnd { name: String, output: serde_json::Value },
|
||||
/// Stream completed
|
||||
Complete { input_tokens: u32, output_tokens: u32 },
|
||||
/// Error occurred
|
||||
Error { message: String },
|
||||
}
|
||||
|
||||
/// Streaming chat request
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StreamChatRequest {
|
||||
/// Agent ID
|
||||
pub agent_id: String,
|
||||
/// Session ID (for event routing)
|
||||
pub session_id: String,
|
||||
/// Message content
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
/// Send a message to an agent with streaming response
|
||||
///
|
||||
/// This command initiates a streaming chat session. Events are emitted
|
||||
/// via Tauri's event system with the name "stream:chunk" and include
|
||||
/// the session_id for routing.
|
||||
#[tauri::command]
|
||||
pub async fn agent_chat_stream(
|
||||
app: AppHandle,
|
||||
state: State<'_, KernelState>,
|
||||
request: StreamChatRequest,
|
||||
) -> Result<(), String> {
|
||||
// Parse agent ID first
|
||||
let id: AgentId = request.agent_id.parse()
|
||||
.map_err(|_| "Invalid agent ID format".to_string())?;
|
||||
|
||||
let session_id = request.session_id.clone();
|
||||
let message = request.message;
|
||||
|
||||
// Get the streaming receiver while holding the lock, then release it
|
||||
let mut rx = {
|
||||
let kernel_lock = state.lock().await;
|
||||
let kernel = kernel_lock.as_ref()
|
||||
.ok_or_else(|| "Kernel not initialized. Call kernel_init first.".to_string())?;
|
||||
|
||||
// Start the stream - this spawns a background task
|
||||
kernel.send_message_stream(&id, message)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to start streaming: {}", e))?
|
||||
};
|
||||
// Lock is released here
|
||||
|
||||
// Spawn a task to process stream events
|
||||
tokio::spawn(async move {
|
||||
use zclaw_runtime::LoopEvent;
|
||||
|
||||
while let Some(event) = rx.recv().await {
|
||||
let stream_event = match event {
|
||||
LoopEvent::Delta(delta) => {
|
||||
StreamChatEvent::Delta { delta }
|
||||
}
|
||||
LoopEvent::ToolStart { name, input } => {
|
||||
StreamChatEvent::ToolStart { name, input }
|
||||
}
|
||||
LoopEvent::ToolEnd { name, output } => {
|
||||
StreamChatEvent::ToolEnd { name, output }
|
||||
}
|
||||
LoopEvent::Complete(result) => {
|
||||
StreamChatEvent::Complete {
|
||||
input_tokens: result.input_tokens,
|
||||
output_tokens: result.output_tokens,
|
||||
}
|
||||
}
|
||||
LoopEvent::Error(message) => {
|
||||
StreamChatEvent::Error { message }
|
||||
}
|
||||
};
|
||||
|
||||
// Emit the event with session_id for routing
|
||||
let _ = app.emit("stream:chunk", serde_json::json!({
|
||||
"sessionId": session_id,
|
||||
"event": stream_event
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create the kernel state for Tauri
|
||||
pub fn create_kernel_state() -> KernelState {
|
||||
Arc::new(Mutex::new(None))
|
||||
|
||||
@@ -1332,6 +1332,7 @@ pub fn run() {
|
||||
kernel_commands::agent_get,
|
||||
kernel_commands::agent_delete,
|
||||
kernel_commands::agent_chat,
|
||||
kernel_commands::agent_chat_stream,
|
||||
// OpenFang commands (new naming)
|
||||
openfang_status,
|
||||
openfang_start,
|
||||
@@ -1347,7 +1348,6 @@ pub fn run() {
|
||||
openfang_process_logs,
|
||||
openfang_version,
|
||||
// Health check commands
|
||||
openfang_health_check,
|
||||
openfang_ping,
|
||||
// Backward-compatible aliases (OpenClaw naming)
|
||||
gateway_status,
|
||||
|
||||
Reference in New Issue
Block a user