feat: refactor sgclaw around zeroclaw compat runtime
This commit is contained in:
197
src/compat/runtime.rs
Normal file
197
src/compat/runtime.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures_util::{stream, StreamExt};
|
||||
use zeroclaw::agent::dispatcher::NativeToolDispatcher;
|
||||
use zeroclaw::agent::{Agent, TurnEvent};
|
||||
use zeroclaw::config::Config as ZeroClawConfig;
|
||||
use zeroclaw::observability::{NoopObserver, Observer};
|
||||
use zeroclaw::providers::{
|
||||
self, ChatMessage, ChatRequest, ChatResponse, Provider,
|
||||
};
|
||||
use zeroclaw::providers::traits::{
|
||||
ProviderCapabilities, StreamEvent, StreamOptions, StreamResult,
|
||||
};
|
||||
|
||||
use crate::compat::browser_tool_adapter::{ZeroClawBrowserTool, BROWSER_ACTION_TOOL_NAME};
|
||||
use crate::compat::config_adapter::build_zeroclaw_config;
|
||||
use crate::compat::event_bridge::log_entry_for_turn_event;
|
||||
use crate::compat::memory_adapter::build_memory;
|
||||
use crate::pipe::{BrowserPipeTool, PipeError, Transport};
|
||||
|
||||
pub fn execute_task<T: Transport + 'static>(
|
||||
transport: &T,
|
||||
browser_tool: BrowserPipeTool<T>,
|
||||
instruction: &str,
|
||||
workspace_root: &Path,
|
||||
) -> Result<String, PipeError> {
|
||||
let config = build_zeroclaw_config(workspace_root)
|
||||
.map_err(|err| PipeError::Protocol(err.to_string()))?;
|
||||
let provider = build_provider(&config)?;
|
||||
let runtime = tokio::runtime::Runtime::new()
|
||||
.map_err(|err| PipeError::Protocol(format!("failed to create tokio runtime: {err}")))?;
|
||||
|
||||
runtime.block_on(execute_task_with_provider(
|
||||
transport,
|
||||
browser_tool,
|
||||
provider,
|
||||
instruction,
|
||||
config,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn execute_task_with_provider<T: Transport + 'static>(
|
||||
transport: &T,
|
||||
browser_tool: BrowserPipeTool<T>,
|
||||
provider: Box<dyn Provider>,
|
||||
instruction: &str,
|
||||
config: ZeroClawConfig,
|
||||
) -> Result<String, PipeError> {
|
||||
let mut agent = build_agent(browser_tool, provider, &config)?;
|
||||
let (event_tx, mut event_rx) = tokio::sync::mpsc::channel::<TurnEvent>(32);
|
||||
let instruction = instruction.to_string();
|
||||
|
||||
let task = tokio::spawn(async move { agent.turn_streamed(&instruction, event_tx).await });
|
||||
|
||||
while let Some(event) = event_rx.recv().await {
|
||||
if let Some(log_entry) = log_entry_for_turn_event(&event) {
|
||||
transport.send(&log_entry)?;
|
||||
}
|
||||
}
|
||||
|
||||
task.await
|
||||
.map_err(|err| PipeError::Protocol(format!("zeroclaw task join failed: {err}")))?
|
||||
.map_err(|err| PipeError::Protocol(err.to_string()))
|
||||
}
|
||||
|
||||
fn build_agent<T: Transport + 'static>(
|
||||
browser_tool: BrowserPipeTool<T>,
|
||||
provider: Box<dyn Provider>,
|
||||
config: &ZeroClawConfig,
|
||||
) -> Result<Agent, PipeError> {
|
||||
let memory = build_memory(config).map_err(map_anyhow_to_pipe_error)?;
|
||||
let observer: Arc<dyn Observer> = Arc::new(NoopObserver);
|
||||
let tools: Vec<Box<dyn zeroclaw::tools::Tool>> =
|
||||
vec![Box::new(ZeroClawBrowserTool::new(browser_tool))];
|
||||
|
||||
Agent::builder()
|
||||
.provider(provider)
|
||||
.tools(tools)
|
||||
.memory(Arc::from(memory))
|
||||
.observer(observer)
|
||||
.tool_dispatcher(Box::new(NativeToolDispatcher))
|
||||
.config(config.agent.clone())
|
||||
.model_name(
|
||||
config
|
||||
.default_model
|
||||
.clone()
|
||||
.unwrap_or_else(|| "deepseek-chat".to_string()),
|
||||
)
|
||||
.temperature(config.default_temperature)
|
||||
.workspace_dir(config.workspace_dir.clone())
|
||||
.allowed_tools(Some(vec![BROWSER_ACTION_TOOL_NAME.to_string()]))
|
||||
.build()
|
||||
.map_err(map_anyhow_to_pipe_error)
|
||||
}
|
||||
|
||||
fn build_provider(config: &ZeroClawConfig) -> Result<Box<dyn Provider>, PipeError> {
|
||||
let provider_name = config.default_provider.as_deref().unwrap_or("deepseek");
|
||||
let model_name = config
|
||||
.default_model
|
||||
.as_deref()
|
||||
.unwrap_or("deepseek-chat");
|
||||
let runtime_options = providers::provider_runtime_options_from_config(config);
|
||||
let resolved_provider_name = if provider_name == "deepseek" {
|
||||
config
|
||||
.api_url
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|url| !url.is_empty())
|
||||
.map(|url| format!("custom:{url}"))
|
||||
.unwrap_or_else(|| provider_name.to_string())
|
||||
} else {
|
||||
provider_name.to_string()
|
||||
};
|
||||
let provider = providers::create_routed_provider_with_options(
|
||||
&resolved_provider_name,
|
||||
config.api_key.as_deref(),
|
||||
config.api_url.as_deref(),
|
||||
&config.reliability,
|
||||
&config.model_routes,
|
||||
model_name,
|
||||
&runtime_options,
|
||||
)
|
||||
.map_err(map_anyhow_to_pipe_error)?;
|
||||
|
||||
Ok(Box::new(NonStreamingProvider::new(provider)))
|
||||
}
|
||||
|
||||
fn map_anyhow_to_pipe_error(err: anyhow::Error) -> PipeError {
|
||||
PipeError::Protocol(err.to_string())
|
||||
}
|
||||
|
||||
struct NonStreamingProvider {
|
||||
inner: Box<dyn Provider>,
|
||||
}
|
||||
|
||||
impl NonStreamingProvider {
|
||||
fn new(inner: Box<dyn Provider>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Provider for NonStreamingProvider {
|
||||
fn capabilities(&self) -> ProviderCapabilities {
|
||||
self.inner.capabilities()
|
||||
}
|
||||
|
||||
async fn chat_with_system(
|
||||
&self,
|
||||
system_prompt: Option<&str>,
|
||||
message: &str,
|
||||
model: &str,
|
||||
temperature: f64,
|
||||
) -> anyhow::Result<String> {
|
||||
self.inner
|
||||
.chat_with_system(system_prompt, message, model, temperature)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn chat_with_history(
|
||||
&self,
|
||||
messages: &[ChatMessage],
|
||||
model: &str,
|
||||
temperature: f64,
|
||||
) -> anyhow::Result<String> {
|
||||
self.inner.chat_with_history(messages, model, temperature).await
|
||||
}
|
||||
|
||||
async fn chat(
|
||||
&self,
|
||||
request: ChatRequest<'_>,
|
||||
model: &str,
|
||||
temperature: f64,
|
||||
) -> anyhow::Result<ChatResponse> {
|
||||
self.inner.chat(request, model, temperature).await
|
||||
}
|
||||
|
||||
fn supports_streaming(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn supports_streaming_tool_events(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn stream_chat(
|
||||
&self,
|
||||
_request: ChatRequest<'_>,
|
||||
_model: &str,
|
||||
_temperature: f64,
|
||||
_options: StreamOptions,
|
||||
) -> stream::BoxStream<'static, StreamResult<StreamEvent>> {
|
||||
stream::empty().boxed()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user