feat: Enhance OAuth module and OpenRouter client functionality
- Updated OAuth module to include unused imports for better clarity. - Refactored OpenRouterClient to improve chat functionality and added support for tools in chat requests. - Modified internet tools (Download, Fetch, Search) to use a more flexible internet mode check. - Introduced new Bash tools for managing background jobs (BashOutput, BashKill). - Enhanced workflow tool to parse and execute workflow scripts with arguments. - Updated status and workflow views to reflect new agent and findings counts. - Added IPC protocol definitions for client requests and state payloads.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
use serde_json::{json, Value};
|
||||
use anyhow::{Result, anyhow};
|
||||
use super::Tool;
|
||||
use super::ToolCtx;
|
||||
|
||||
pub struct BashOutput;
|
||||
|
||||
impl Tool for BashOutput {
|
||||
fn name(&self) -> &'static str {
|
||||
"bash_output"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Retrieve output from a background bash job by job_id"
|
||||
}
|
||||
|
||||
fn parameters(&self) -> Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"job_id": {
|
||||
"type": "string",
|
||||
"description": "Job ID returned by bash with run_in_background=true"
|
||||
}
|
||||
},
|
||||
"required": ["job_id"]
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result<String> {
|
||||
let job_id = args.get("job_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow!("missing required argument: job_id"))?
|
||||
.to_string();
|
||||
match crate::app::bgbash::control::bash_output(&job_id) {
|
||||
Some(lines) => Ok(lines.join("\n")),
|
||||
None => Ok(format!("No new output from job '{}'", job_id)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BashKill;
|
||||
|
||||
impl Tool for BashKill {
|
||||
fn name(&self) -> &'static str {
|
||||
"bash_kill"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Kill a background bash job by job_id"
|
||||
}
|
||||
|
||||
fn parameters(&self) -> Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"job_id": {
|
||||
"type": "string",
|
||||
"description": "Job ID returned by bash with run_in_background=true"
|
||||
}
|
||||
},
|
||||
"required": ["job_id"]
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result<String> {
|
||||
let job_id = args.get("job_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow!("missing required argument: job_id"))?
|
||||
.to_string();
|
||||
crate::app::bgbash::control::bash_kill(&job_id)?;
|
||||
Ok(format!("Killed background job '{}'", job_id))
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,8 @@ impl Tool for Download {
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
|
||||
if ctx.internet_mode == crate::model::settings::InternetMode::Off {
|
||||
anyhow::bail!("internet access is disabled. Enable it in settings to use download.");
|
||||
if !ctx.internet_mode.can_download() {
|
||||
anyhow::bail!("download requires internet mode Full, current mode: {:?}", ctx.internet_mode);
|
||||
}
|
||||
let url = args.get("url")
|
||||
.and_then(|v| v.as_str())
|
||||
|
||||
@@ -29,8 +29,8 @@ impl Tool for Fetch {
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
|
||||
if ctx.internet_mode == crate::model::settings::InternetMode::Off {
|
||||
anyhow::bail!("internet access is disabled. Enable it in settings to use fetch.");
|
||||
if !ctx.internet_mode.can_fetch() {
|
||||
anyhow::bail!("fetch requires internet mode Full, current mode: {:?}", ctx.internet_mode);
|
||||
}
|
||||
let url = args.get("url")
|
||||
.and_then(|v| v.as_str())
|
||||
|
||||
@@ -28,8 +28,8 @@ impl Tool for Search {
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
|
||||
if ctx.internet_mode == crate::model::settings::InternetMode::Off {
|
||||
anyhow::bail!("internet access is disabled. Enable it in settings to use web_search.");
|
||||
if !ctx.internet_mode.can_search() {
|
||||
anyhow::bail!("web_search requires internet mode Full, current mode: {:?}", ctx.internet_mode);
|
||||
}
|
||||
let query = args.get("query")
|
||||
.and_then(|v| v.as_str())
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::path::PathBuf;
|
||||
use serde_json::Value;
|
||||
use anyhow::Result;
|
||||
|
||||
pub mod bash_tools;
|
||||
pub mod fs;
|
||||
pub mod git_cred;
|
||||
pub mod git_operator;
|
||||
@@ -28,6 +29,7 @@ pub struct GraduatedCheck {
|
||||
pub rule: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToolCtx {
|
||||
pub workspaces: Vec<PathBuf>,
|
||||
pub session_dir: PathBuf,
|
||||
@@ -116,6 +118,8 @@ pub fn all_tools() -> Vec<Box<dyn Tool>> {
|
||||
Box::new(super::tool::fs::edit::Edit),
|
||||
Box::new(super::tool::search::Grep),
|
||||
Box::new(super::tool::search::Glob),
|
||||
Box::new(super::tool::bash_tools::BashOutput),
|
||||
Box::new(super::tool::bash_tools::BashKill),
|
||||
Box::new(super::tool::shell::Bash),
|
||||
Box::new(super::tool::git_operator::GitOperator),
|
||||
Box::new(super::tool::git_worktree::GitWorktree),
|
||||
@@ -124,6 +128,9 @@ pub fn all_tools() -> Vec<Box<dyn Tool>> {
|
||||
Box::new(super::tool::plan::PlanEnter),
|
||||
Box::new(super::tool::plan::PlanReady),
|
||||
Box::new(super::tool::workflow::WorkflowRun),
|
||||
Box::new(super::tool::internet::fetch::Fetch),
|
||||
Box::new(super::tool::internet::download::Download),
|
||||
Box::new(super::tool::internet::search::Search),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -131,6 +138,20 @@ pub fn tool_is_risky(name: &str) -> bool {
|
||||
matches!(name, "write" | "delete" | "edit" | "bash" | "git_operator")
|
||||
}
|
||||
|
||||
pub fn tool_defs(tools: &[Box<dyn Tool>]) -> Vec<crate::dto::openrouter::request::ToolDef> {
|
||||
tools
|
||||
.iter()
|
||||
.map(|t| crate::dto::openrouter::request::ToolDef {
|
||||
type_: "function".to_string(),
|
||||
function: crate::dto::openrouter::request::ToolFunctionDef {
|
||||
name: t.name().to_string(),
|
||||
description: t.description().to_string(),
|
||||
parameters: t.parameters(),
|
||||
},
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub const DEFERRED_TOOLS: &[&str] = &[
|
||||
"read", "write", "edit", "bash", "grep", "glob",
|
||||
"git_operator", "git_worktree", "git_cred",
|
||||
|
||||
@@ -31,6 +31,10 @@ impl Tool for Bash {
|
||||
"timeout": {
|
||||
"type": "integer",
|
||||
"description": "Timeout in milliseconds (default 120000, max 600000)"
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Run the command in the background and return immediately with a job ID"
|
||||
}
|
||||
},
|
||||
"required": ["command"]
|
||||
@@ -47,6 +51,11 @@ impl Tool for Bash {
|
||||
let workspace_roots: Vec<&std::path::Path> = ctx.workspaces.iter().map(|p| p.as_path()).collect();
|
||||
crate::app::catastrophic::CatastrophicGuard::check_all(&cmd, &workspace_roots)
|
||||
.map_err(|e| anyhow!("catastrophic guard blocked: {}", e))?;
|
||||
let run_in_background = args.get("run_in_background").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
if run_in_background {
|
||||
let job = crate::app::bgbash::job::spawn_bash_job(cmd);
|
||||
return Ok(format!("Background job: {}", job.id));
|
||||
}
|
||||
let mut child = Command::new("bash")
|
||||
.arg("-c")
|
||||
.arg(&cmd)
|
||||
|
||||
+16
-3
@@ -32,10 +32,23 @@ impl Tool for WorkflowRun {
|
||||
}
|
||||
|
||||
fn run(&self, _ctx: &ToolCtx, args: &Value) -> Result<String> {
|
||||
let _script = args.get("script")
|
||||
let script_str = args.get("script")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow!("missing required argument: script"))?;
|
||||
let _workflow_args = args.get("args");
|
||||
Ok("workflow delegated to workflow engine".to_string())
|
||||
|
||||
let workflow_script: crate::app::workflow::script::WorkflowScript =
|
||||
serde_json::from_str(script_str)
|
||||
.map_err(|e| anyhow!("failed to parse workflow script: {}", e))?;
|
||||
|
||||
let workflow_args: std::collections::HashMap<String, String> = args.get("args")
|
||||
.and_then(|v| v.as_object())
|
||||
.map(|obj| {
|
||||
obj.iter().filter_map(|(k, v)| {
|
||||
v.as_str().map(|s| (k.clone(), s.to_string()))
|
||||
}).collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
crate::app::workflow::engine::run_workflow(&workflow_script, &workflow_args)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user