mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
ci: Added CI/CD and release pipelines (#45)
This commit is contained in:
@@ -469,7 +469,7 @@ pub fn create_wasm_channel_router(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::channels::wasm::capabilities::ChannelCapabilities;
|
||||
|
||||
@@ -43,12 +43,12 @@ use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView};
|
||||
use crate::channels::wasm::capabilities::ChannelCapabilities;
|
||||
use crate::channels::wasm::error::WasmChannelError;
|
||||
use crate::channels::wasm::host::{ChannelEmitRateLimiter, ChannelHostState, EmittedMessage};
|
||||
use crate::pairing::PairingStore;
|
||||
use crate::channels::wasm::router::RegisteredEndpoint;
|
||||
use crate::channels::wasm::runtime::{PreparedChannelModule, WasmChannelRuntime};
|
||||
use crate::channels::wasm::schema::ChannelConfig;
|
||||
use crate::channels::{Channel, IncomingMessage, MessageStream, OutgoingResponse, StatusUpdate};
|
||||
use crate::error::ChannelError;
|
||||
use crate::pairing::PairingStore;
|
||||
use crate::safety::LeakDetector;
|
||||
use crate::tools::wasm::LogLevel;
|
||||
use crate::tools::wasm::WasmResourceLimiter;
|
||||
@@ -1190,6 +1190,7 @@ impl WasmChannel {
|
||||
///
|
||||
/// Static method for use by the background typing repeat task (which
|
||||
/// doesn't have access to `&self`).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn execute_status(
|
||||
channel_name: &str,
|
||||
runtime: &Arc<WasmChannelRuntime>,
|
||||
@@ -2073,12 +2074,12 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::channels::Channel;
|
||||
use crate::pairing::PairingStore;
|
||||
use crate::channels::wasm::capabilities::ChannelCapabilities;
|
||||
use crate::channels::wasm::runtime::{
|
||||
PreparedChannelModule, WasmChannelRuntime, WasmChannelRuntimeConfig,
|
||||
};
|
||||
use crate::channels::wasm::wrapper::{HttpResponse, WasmChannel};
|
||||
use crate::pairing::PairingStore;
|
||||
use crate::tools::wasm::ResourceLimits;
|
||||
|
||||
fn create_test_channel() -> WasmChannel {
|
||||
@@ -2525,8 +2526,13 @@ mod tests {
|
||||
);
|
||||
creds.insert("OTHER_SECRET".to_string(), "s3cret".to_string());
|
||||
|
||||
let store =
|
||||
ChannelStoreData::new(1024 * 1024, "test", ChannelCapabilities::default(), creds);
|
||||
let store = ChannelStoreData::new(
|
||||
1024 * 1024,
|
||||
"test",
|
||||
ChannelCapabilities::default(),
|
||||
creds,
|
||||
Arc::new(PairingStore::new()),
|
||||
);
|
||||
|
||||
let error = "HTTP request failed: error sending request for url \
|
||||
(https://api.telegram.org/bot8218490433:AAEZeUxwqZ5OO3mOCXv7fKvpdhDgsmBBNis/getUpdates)";
|
||||
@@ -2556,6 +2562,7 @@ mod tests {
|
||||
"test",
|
||||
ChannelCapabilities::default(),
|
||||
std::collections::HashMap::new(),
|
||||
Arc::new(PairingStore::new()),
|
||||
);
|
||||
|
||||
let input = "some error message";
|
||||
@@ -2569,8 +2576,13 @@ mod tests {
|
||||
let mut creds = std::collections::HashMap::new();
|
||||
creds.insert("EMPTY_TOKEN".to_string(), String::new());
|
||||
|
||||
let store =
|
||||
ChannelStoreData::new(1024 * 1024, "test", ChannelCapabilities::default(), creds);
|
||||
let store = ChannelStoreData::new(
|
||||
1024 * 1024,
|
||||
"test",
|
||||
ChannelCapabilities::default(),
|
||||
creds,
|
||||
Arc::new(PairingStore::new()),
|
||||
);
|
||||
|
||||
let input = "should not match anything";
|
||||
assert_eq!(store.redact_credentials(input), input);
|
||||
|
||||
+3
-1
@@ -71,7 +71,9 @@ pub async fn run_config_command(cmd: ConfigCommand) -> anyhow::Result<()> {
|
||||
|
||||
/// Bootstrap a DB connection for config commands.
|
||||
async fn connect_store() -> anyhow::Result<crate::history::Store> {
|
||||
let config = crate::config::Config::from_env().await.map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
let config = crate::config::Config::from_env()
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
let store = crate::history::Store::new(&config.database).await?;
|
||||
store.run_migrations().await?;
|
||||
Ok(store)
|
||||
|
||||
+9
-5
@@ -52,7 +52,10 @@ fn run_list(store: &PairingStore, channel: &str, json: bool) -> Result<(), Strin
|
||||
let requests = store.list_pending(channel).map_err(|e| e.to_string())?;
|
||||
|
||||
if json {
|
||||
println!("{}", serde_json::to_string_pretty(&requests).map_err(|e| e.to_string())?);
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&requests).map_err(|e| e.to_string())?
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -69,9 +72,7 @@ fn run_list(store: &PairingStore, channel: &str, json: bool) -> Result<(), Strin
|
||||
.and_then(|m| m.as_object())
|
||||
.map(|o| {
|
||||
o.iter()
|
||||
.filter_map(|(k, v)| {
|
||||
v.as_str().map(|s| format!("{}={}", k, s))
|
||||
})
|
||||
.filter_map(|(k, v)| v.as_str().map(|s| format!("{}={}", k, s)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
})
|
||||
@@ -88,7 +89,10 @@ fn run_approve(store: &PairingStore, channel: &str, code: &str) -> Result<(), St
|
||||
println!("Approved {} sender {}.", channel, entry.id);
|
||||
Ok(())
|
||||
}
|
||||
Ok(None) => Err(format!("No pending pairing request found for code: {}", code)),
|
||||
Ok(None) => Err(format!(
|
||||
"No pending pairing request found for code: {}",
|
||||
code
|
||||
)),
|
||||
Err(crate::pairing::PairingStoreError::ApproveRateLimited) => Err(
|
||||
"Too many failed approve attempts. Wait a few minutes before trying again.".to_string(),
|
||||
),
|
||||
|
||||
+1
-1
@@ -43,7 +43,6 @@ pub mod bootstrap;
|
||||
pub mod channels;
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod pairing;
|
||||
pub mod context;
|
||||
pub mod error;
|
||||
pub mod estimation;
|
||||
@@ -52,6 +51,7 @@ pub mod extensions;
|
||||
pub mod history;
|
||||
pub mod llm;
|
||||
pub mod orchestrator;
|
||||
pub mod pairing;
|
||||
pub mod safety;
|
||||
pub mod sandbox;
|
||||
pub mod secrets;
|
||||
|
||||
+4
-2
@@ -7,7 +7,6 @@ use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitEx
|
||||
|
||||
use ironclaw::{
|
||||
agent::{Agent, AgentDeps, SessionManager},
|
||||
pairing::PairingStore,
|
||||
channels::{
|
||||
ChannelManager, GatewayChannel, HttpChannel, ReplChannel, WebhookServer,
|
||||
WebhookServerConfig,
|
||||
@@ -30,6 +29,7 @@ use ironclaw::{
|
||||
ContainerJobConfig, ContainerJobManager, OrchestratorApi, TokenStore,
|
||||
api::OrchestratorState,
|
||||
},
|
||||
pairing::PairingStore,
|
||||
safety::SafetyLayer,
|
||||
secrets::{PostgresSecretsStore, SecretsCrypto, SecretsStore},
|
||||
setup::{SetupConfig, SetupWizard},
|
||||
@@ -86,7 +86,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
// Memory commands need database (and optionally embeddings)
|
||||
let _ = dotenvy::dotenv();
|
||||
let config = Config::from_env().await.map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
let config = Config::from_env()
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
let store = ironclaw::history::Store::new(&config.database).await?;
|
||||
store.run_migrations().await?;
|
||||
|
||||
|
||||
+61
-31
@@ -5,7 +5,7 @@
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use fs4::FileExt;
|
||||
@@ -94,17 +94,17 @@ fn safe_channel_key(channel: &str) -> Result<String, PairingStoreError> {
|
||||
Ok(safe)
|
||||
}
|
||||
|
||||
fn pairing_path(base_dir: &PathBuf, channel: &str) -> Result<PathBuf, PairingStoreError> {
|
||||
fn pairing_path(base_dir: &Path, channel: &str) -> Result<PathBuf, PairingStoreError> {
|
||||
let key = safe_channel_key(channel)?;
|
||||
Ok(base_dir.join(format!("{}-pairing.json", key)))
|
||||
}
|
||||
|
||||
fn allow_from_path(base_dir: &PathBuf, channel: &str) -> Result<PathBuf, PairingStoreError> {
|
||||
fn allow_from_path(base_dir: &Path, channel: &str) -> Result<PathBuf, PairingStoreError> {
|
||||
let key = safe_channel_key(channel)?;
|
||||
Ok(base_dir.join(format!("{}-allowFrom.json", key)))
|
||||
}
|
||||
|
||||
fn approve_attempts_path(base_dir: &PathBuf, channel: &str) -> Result<PathBuf, PairingStoreError> {
|
||||
fn approve_attempts_path(base_dir: &Path, channel: &str) -> Result<PathBuf, PairingStoreError> {
|
||||
let key = safe_channel_key(channel)?;
|
||||
Ok(base_dir.join(format!("{}-approve-attempts.json", key)))
|
||||
}
|
||||
@@ -236,10 +236,11 @@ impl PairingStore {
|
||||
file.lock_exclusive()?;
|
||||
|
||||
let content = fs::read_to_string(&path).unwrap_or_default();
|
||||
let mut store: PairingStoreFile = serde_json::from_str(&content).unwrap_or(PairingStoreFile {
|
||||
version: 1,
|
||||
requests: Vec::new(),
|
||||
});
|
||||
let mut store: PairingStoreFile =
|
||||
serde_json::from_str(&content).unwrap_or(PairingStoreFile {
|
||||
version: 1,
|
||||
requests: Vec::new(),
|
||||
});
|
||||
|
||||
let now = now_iso();
|
||||
let now_secs = now_secs();
|
||||
@@ -296,7 +297,10 @@ impl PairingStore {
|
||||
self.write_pairing_file_locked(&mut file, channel, &store.requests)?;
|
||||
fs4::FileExt::unlock(&file)?;
|
||||
|
||||
Ok(UpsertResult { code, created: true })
|
||||
Ok(UpsertResult {
|
||||
code,
|
||||
created: true,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_approve_rate_limited(&self, channel: &str) -> Result<bool, PairingStoreError> {
|
||||
@@ -306,8 +310,7 @@ impl PairingStore {
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(false),
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
let mut data: ApproveAttemptsFile =
|
||||
serde_json::from_str(&content).unwrap_or_default();
|
||||
let mut data: ApproveAttemptsFile = serde_json::from_str(&content).unwrap_or_default();
|
||||
let now = now_secs();
|
||||
let cutoff = now.saturating_sub(PAIRING_APPROVE_RATE_WINDOW_SECS);
|
||||
data.failed_at.retain(|&t| t >= cutoff);
|
||||
@@ -321,11 +324,11 @@ impl PairingStore {
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&path)?;
|
||||
file.lock_exclusive()?;
|
||||
let content = fs::read_to_string(&path).unwrap_or_default();
|
||||
let mut data: ApproveAttemptsFile =
|
||||
serde_json::from_str(&content).unwrap_or_default();
|
||||
let mut data: ApproveAttemptsFile = serde_json::from_str(&content).unwrap_or_default();
|
||||
let now = now_secs();
|
||||
data.failed_at.push(now);
|
||||
let cutoff = now.saturating_sub(PAIRING_APPROVE_RATE_WINDOW_SECS);
|
||||
@@ -368,10 +371,11 @@ impl PairingStore {
|
||||
file.lock_exclusive()?;
|
||||
|
||||
let content = fs::read_to_string(&path).unwrap_or_default();
|
||||
let mut store: PairingStoreFile = serde_json::from_str(&content).unwrap_or(PairingStoreFile {
|
||||
version: 1,
|
||||
requests: Vec::new(),
|
||||
});
|
||||
let mut store: PairingStoreFile =
|
||||
serde_json::from_str(&content).unwrap_or(PairingStoreFile {
|
||||
version: 1,
|
||||
requests: Vec::new(),
|
||||
});
|
||||
|
||||
let now_secs = now_secs();
|
||||
store.requests.retain(|r| !is_expired(r, now_secs));
|
||||
@@ -409,10 +413,11 @@ impl PairingStore {
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
let file: AllowFromStoreFile = serde_json::from_str(&content).unwrap_or(AllowFromStoreFile {
|
||||
version: 1,
|
||||
allow_from: Vec::new(),
|
||||
});
|
||||
let file: AllowFromStoreFile =
|
||||
serde_json::from_str(&content).unwrap_or(AllowFromStoreFile {
|
||||
version: 1,
|
||||
allow_from: Vec::new(),
|
||||
});
|
||||
|
||||
Ok(file.allow_from)
|
||||
}
|
||||
@@ -433,10 +438,9 @@ impl PairingStore {
|
||||
if let Some(u) = username {
|
||||
let u = u.trim().to_lowercase();
|
||||
let u_norm = u.strip_prefix('@').unwrap_or(&u);
|
||||
if allow
|
||||
.iter()
|
||||
.any(|e| e.trim().to_lowercase() == u || e.trim().to_lowercase() == format!("@{}", u_norm))
|
||||
{
|
||||
if allow.iter().any(|e| {
|
||||
e.trim().to_lowercase() == u || e.trim().to_lowercase() == format!("@{}", u_norm)
|
||||
}) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
@@ -456,6 +460,7 @@ impl PairingStore {
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&path)?;
|
||||
|
||||
file.lock_exclusive()?;
|
||||
@@ -563,11 +568,20 @@ mod tests {
|
||||
fn test_upsert_request_creates_new() {
|
||||
let (store, _) = test_store();
|
||||
let result = store
|
||||
.upsert_request("telegram", "user123", Some(serde_json::json!({"chat_id": 456})))
|
||||
.upsert_request(
|
||||
"telegram",
|
||||
"user123",
|
||||
Some(serde_json::json!({"chat_id": 456})),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.created);
|
||||
assert_eq!(result.code.len(), PAIRING_CODE_LENGTH);
|
||||
assert!(result.code.chars().all(|c| PAIRING_ALPHABET.contains(&(c as u8))));
|
||||
assert!(
|
||||
result
|
||||
.code
|
||||
.chars()
|
||||
.all(|c| PAIRING_ALPHABET.contains(&(c as u8)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -575,7 +589,9 @@ mod tests {
|
||||
let (store, _) = test_store();
|
||||
let r1 = store.upsert_request("telegram", "user123", None).unwrap();
|
||||
assert!(r1.created);
|
||||
let r2 = store.upsert_request("telegram", "user123", Some(serde_json::json!({"x": 1}))).unwrap();
|
||||
let r2 = store
|
||||
.upsert_request("telegram", "user123", Some(serde_json::json!({"x": 1})))
|
||||
.unwrap();
|
||||
assert!(!r2.created);
|
||||
assert_eq!(r1.code, r2.code);
|
||||
|
||||
@@ -633,21 +649,35 @@ mod tests {
|
||||
let r = store.upsert_request("telegram", "user999", None).unwrap();
|
||||
store.approve("telegram", &r.code).unwrap();
|
||||
|
||||
assert!(store.is_sender_allowed("telegram", "user999", None).unwrap());
|
||||
assert!(
|
||||
store
|
||||
.is_sender_allowed("telegram", "user999", None)
|
||||
.unwrap()
|
||||
);
|
||||
assert!(!store.is_sender_allowed("telegram", "other", None).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_sender_allowed_by_username() {
|
||||
let (store, _) = test_store();
|
||||
store.upsert_request("telegram", "alice", Some(serde_json::json!({"username": "alice"}))).unwrap();
|
||||
store
|
||||
.upsert_request(
|
||||
"telegram",
|
||||
"alice",
|
||||
Some(serde_json::json!({"username": "alice"})),
|
||||
)
|
||||
.unwrap();
|
||||
let pending = store.list_pending("telegram").unwrap();
|
||||
store.approve("telegram", &pending[0].code).unwrap();
|
||||
|
||||
// approve adds id to allow_from. For username we need to add it manually.
|
||||
// Actually approve adds entry.id which is "alice". So is_sender_allowed("telegram", "alice", None) would work.
|
||||
assert!(store.is_sender_allowed("telegram", "alice", None).unwrap());
|
||||
assert!(store.is_sender_allowed("telegram", "alice", Some("alice")).unwrap());
|
||||
assert!(
|
||||
store
|
||||
.is_sender_allowed("telegram", "alice", Some("alice"))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+8
-18
@@ -101,15 +101,13 @@ mod platform {
|
||||
let ss = SecretService::connect(EncryptionType::Dh)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
SecretError::KeychainError(format!(
|
||||
"Failed to connect to secret service: {}",
|
||||
e
|
||||
))
|
||||
SecretError::KeychainError(format!("Failed to connect to secret service: {}", e))
|
||||
})?;
|
||||
|
||||
let collection = ss.get_default_collection().await.map_err(|e| {
|
||||
SecretError::KeychainError(format!("Failed to get collection: {}", e))
|
||||
})?;
|
||||
let collection = ss
|
||||
.get_default_collection()
|
||||
.await
|
||||
.map_err(|e| SecretError::KeychainError(format!("Failed to get collection: {}", e)))?;
|
||||
|
||||
// Unlock if needed
|
||||
if collection.is_locked().await.unwrap_or(true) {
|
||||
@@ -132,9 +130,7 @@ mod platform {
|
||||
"text/plain",
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
SecretError::KeychainError(format!("Failed to create secret: {}", e))
|
||||
})?;
|
||||
.map_err(|e| SecretError::KeychainError(format!("Failed to create secret: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -144,10 +140,7 @@ mod platform {
|
||||
let ss = SecretService::connect(EncryptionType::Dh)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
SecretError::KeychainError(format!(
|
||||
"Failed to connect to secret service: {}",
|
||||
e
|
||||
))
|
||||
SecretError::KeychainError(format!("Failed to connect to secret service: {}", e))
|
||||
})?;
|
||||
|
||||
let items = ss
|
||||
@@ -188,10 +181,7 @@ mod platform {
|
||||
let ss = SecretService::connect(EncryptionType::Dh)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
SecretError::KeychainError(format!(
|
||||
"Failed to connect to secret service: {}",
|
||||
e
|
||||
))
|
||||
SecretError::KeychainError(format!("Failed to connect to secret service: {}", e))
|
||||
})?;
|
||||
|
||||
let items = ss
|
||||
|
||||
@@ -192,9 +192,10 @@ impl CreateSecretParams {
|
||||
}
|
||||
|
||||
/// Where a credential should be injected in an HTTP request.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub enum CredentialLocation {
|
||||
/// Inject as Authorization header (e.g., "Bearer {secret}")
|
||||
#[default]
|
||||
AuthorizationBearer,
|
||||
/// Inject as Authorization header with Basic auth
|
||||
AuthorizationBasic { username: String },
|
||||
@@ -209,12 +210,6 @@ pub enum CredentialLocation {
|
||||
UrlPath { placeholder: String },
|
||||
}
|
||||
|
||||
impl Default for CredentialLocation {
|
||||
fn default() -> Self {
|
||||
Self::AuthorizationBearer
|
||||
}
|
||||
}
|
||||
|
||||
/// Mapping from a secret name to where it should be injected.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CredentialMapping {
|
||||
|
||||
+3
-6
@@ -782,12 +782,9 @@ impl SetupWizard {
|
||||
fn save_and_summarize(&mut self) -> Result<(), SetupError> {
|
||||
self.settings.onboard_completed = true;
|
||||
|
||||
self.settings.save().map_err(|e| {
|
||||
SetupError::Io(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("Failed to save settings: {}", e),
|
||||
))
|
||||
})?;
|
||||
self.settings
|
||||
.save()
|
||||
.map_err(|e| std::io::Error::other(format!("Failed to save settings: {}", e)))?;
|
||||
|
||||
println!();
|
||||
print_success("Configuration saved to ~/.ironclaw/");
|
||||
|
||||
@@ -36,7 +36,7 @@ fn is_workspace_path(path: &str) -> bool {
|
||||
.and_then(|f| f.to_str())
|
||||
.unwrap_or(path);
|
||||
|
||||
WORKSPACE_FILES.iter().any(|ws| *ws == filename)
|
||||
WORKSPACE_FILES.contains(&filename)
|
||||
|| path.starts_with("daily/")
|
||||
|| path.starts_with("context/")
|
||||
}
|
||||
|
||||
@@ -365,12 +365,7 @@ pub async fn save_mcp_servers_to_db(
|
||||
store
|
||||
.set_setting(user_id, "mcp_servers", &value)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ConfigError::Io(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
e.to_string(),
|
||||
))
|
||||
})?;
|
||||
.map_err(std::io::Error::other)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -422,7 +422,7 @@ impl Default for ToolRegistry {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tools::tool::EchoTool;
|
||||
use crate::tools::registry::EchoTool;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_register_and_get() {
|
||||
|
||||
+47
-47
@@ -199,57 +199,57 @@ pub trait Tool: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple no-op tool for testing.
|
||||
#[derive(Debug)]
|
||||
pub struct EchoTool;
|
||||
|
||||
#[async_trait]
|
||||
impl Tool for EchoTool {
|
||||
fn name(&self) -> &str {
|
||||
"echo"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Echoes back the input message. Useful for testing."
|
||||
}
|
||||
|
||||
fn parameters_schema(&self) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "The message to echo back"
|
||||
}
|
||||
},
|
||||
"required": ["message"]
|
||||
})
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
params: serde_json::Value,
|
||||
_ctx: &JobContext,
|
||||
) -> Result<ToolOutput, ToolError> {
|
||||
let message = params
|
||||
.get("message")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| {
|
||||
ToolError::InvalidParameters("missing 'message' parameter".to_string())
|
||||
})?;
|
||||
|
||||
Ok(ToolOutput::text(message, Duration::from_millis(1)))
|
||||
}
|
||||
|
||||
fn requires_sanitization(&self) -> bool {
|
||||
false // Echo is a trusted internal tool
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A simple no-op tool for testing.
|
||||
#[derive(Debug)]
|
||||
pub struct EchoTool;
|
||||
|
||||
#[async_trait]
|
||||
impl Tool for EchoTool {
|
||||
fn name(&self) -> &str {
|
||||
"echo"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Echoes back the input message. Useful for testing."
|
||||
}
|
||||
|
||||
fn parameters_schema(&self) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "The message to echo back"
|
||||
}
|
||||
},
|
||||
"required": ["message"]
|
||||
})
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
params: serde_json::Value,
|
||||
_ctx: &JobContext,
|
||||
) -> Result<ToolOutput, ToolError> {
|
||||
let message = params
|
||||
.get("message")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| {
|
||||
ToolError::InvalidParameters("missing 'message' parameter".to_string())
|
||||
})?;
|
||||
|
||||
Ok(ToolOutput::text(message, Duration::from_millis(1)))
|
||||
}
|
||||
|
||||
fn requires_sanitization(&self) -> bool {
|
||||
false // Echo is a trusted internal tool
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_echo_tool() {
|
||||
let tool = EchoTool;
|
||||
|
||||
@@ -404,14 +404,13 @@ impl Repository {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let vector_results = if config.use_vector && embedding.is_some() {
|
||||
self.vector_search(
|
||||
user_id,
|
||||
agent_id,
|
||||
embedding.unwrap(),
|
||||
config.pre_fusion_limit,
|
||||
)
|
||||
.await?
|
||||
let vector_results = if config.use_vector {
|
||||
if let Some(embedding) = embedding {
|
||||
self.vector_search(user_id, agent_id, embedding, config.pre_fusion_limit)
|
||||
.await?
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user