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:
asepharyana
2026-07-11 20:21:59 +07:00
parent c1ad206a00
commit 802d9677ae
42 changed files with 1423 additions and 925 deletions
+74
View File
@@ -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))
}
}
+2 -2
View File
@@ -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())
+2 -2
View File
@@ -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())
+2 -2
View File
@@ -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())
+21
View File
@@ -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",
+9
View File
@@ -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
View File
@@ -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)
}
}