250 lines
7.0 KiB
Rust
250 lines
7.0 KiB
Rust
use ai::error::{AiError, AiResult};
|
|
use ai::tool::tools::FunctionCall;
|
|
use async_trait::async_trait;
|
|
use git::rpc::proto as p;
|
|
use git::rpc::proto::merge_service_client::MergeServiceClient;
|
|
use serde_json::{Value, json};
|
|
|
|
use super::helpers::{arg_str, git_ctx, require_repo_member, rpc_err};
|
|
use crate::agent::run::AppAgentContext;
|
|
|
|
pub struct GitMergeBaseTool;
|
|
|
|
impl GitMergeBaseTool {
|
|
pub fn new() -> Self {
|
|
Self
|
|
}
|
|
}
|
|
|
|
impl Default for GitMergeBaseTool {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl FunctionCall for GitMergeBaseTool {
|
|
type Context = AppAgentContext;
|
|
|
|
fn name(&self) -> &'static str {
|
|
"git_merge_base"
|
|
}
|
|
|
|
fn description(&self) -> &'static str {
|
|
"Find the common ancestor (merge base) of two commits."
|
|
}
|
|
|
|
fn schema(&self) -> Value {
|
|
json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"workspace": { "type": "string", "description": "Workspace name" },
|
|
"repo": { "type": "string", "description": "Repository name" },
|
|
"oid_a": { "type": "string", "description": "First commit OID" },
|
|
"oid_b": { "type": "string", "description": "Second commit OID" }
|
|
},
|
|
"required": ["workspace", "repo", "oid_a", "oid_b"]
|
|
})
|
|
}
|
|
|
|
async fn call(
|
|
&self,
|
|
ctx: &mut AppAgentContext,
|
|
args: Value,
|
|
) -> AiResult<Value> {
|
|
let git = git_ctx(ctx)?;
|
|
let workspace = arg_str(&args, "workspace")?;
|
|
let repo_name = arg_str(&args, "repo")?;
|
|
let oid_a = arg_str(&args, "oid_a")?;
|
|
let oid_b = arg_str(&args, "oid_b")?;
|
|
|
|
let repo =
|
|
require_repo_member(git, ctx.user_id, workspace, repo_name).await?;
|
|
|
|
let mut client = MergeServiceClient::new(git.channel.clone());
|
|
let resp = client
|
|
.merge_base(p::MergeBaseRequest {
|
|
repo_id: repo.id.to_string(),
|
|
oid_a: Some(p::ObjectId {
|
|
value: oid_a.to_string(),
|
|
}),
|
|
oid_b: Some(p::ObjectId {
|
|
value: oid_b.to_string(),
|
|
}),
|
|
})
|
|
.await
|
|
.map_err(rpc_err)?
|
|
.into_inner();
|
|
|
|
Ok(json!({ "base_oid": resp.base_oid.map(|o| o.value) }))
|
|
}
|
|
}
|
|
|
|
pub struct GitMergeAnalysisTool;
|
|
|
|
impl GitMergeAnalysisTool {
|
|
pub fn new() -> Self {
|
|
Self
|
|
}
|
|
}
|
|
|
|
impl Default for GitMergeAnalysisTool {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl FunctionCall for GitMergeAnalysisTool {
|
|
type Context = AppAgentContext;
|
|
|
|
fn name(&self) -> &'static str {
|
|
"git_merge_analysis"
|
|
}
|
|
|
|
fn description(&self) -> &'static str {
|
|
"Analyze whether two commits can be merged (fast-forward, normal, up-to-date, etc)."
|
|
}
|
|
|
|
fn schema(&self) -> Value {
|
|
json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"workspace": { "type": "string", "description": "Workspace name" },
|
|
"repo": { "type": "string", "description": "Repository name" },
|
|
"oid_a": { "type": "string", "description": "First commit OID" },
|
|
"oid_b": { "type": "string", "description": "Second commit OID" }
|
|
},
|
|
"required": ["workspace", "repo", "oid_a", "oid_b"]
|
|
})
|
|
}
|
|
|
|
async fn call(
|
|
&self,
|
|
ctx: &mut AppAgentContext,
|
|
args: Value,
|
|
) -> AiResult<Value> {
|
|
let git = git_ctx(ctx)?;
|
|
let workspace = arg_str(&args, "workspace")?;
|
|
let repo_name = arg_str(&args, "repo")?;
|
|
let oid_a = arg_str(&args, "oid_a")?;
|
|
let oid_b = arg_str(&args, "oid_b")?;
|
|
|
|
let repo =
|
|
require_repo_member(git, ctx.user_id, workspace, repo_name).await?;
|
|
|
|
let mut client = MergeServiceClient::new(git.channel.clone());
|
|
let resp = client
|
|
.merge_analysis(p::MergeAnalysisRequest {
|
|
repo_id: repo.id.to_string(),
|
|
oid_a: Some(p::ObjectId {
|
|
value: oid_a.to_string(),
|
|
}),
|
|
oid_b: Some(p::ObjectId {
|
|
value: oid_b.to_string(),
|
|
}),
|
|
})
|
|
.await
|
|
.map_err(rpc_err)?
|
|
.into_inner();
|
|
|
|
let analysis = resp
|
|
.analysis
|
|
.ok_or_else(|| AiError::Response("no analysis".to_string()))?;
|
|
let pref = resp
|
|
.preference
|
|
.ok_or_else(|| AiError::Response("no preference".to_string()))?;
|
|
|
|
// Determine overall status
|
|
let status = if analysis.is_up_to_date {
|
|
"up_to_date"
|
|
} else if analysis.is_fast_forward {
|
|
"fast_forward"
|
|
} else if analysis.is_normal {
|
|
"normal_merge"
|
|
} else if analysis.is_unborn {
|
|
"unborn"
|
|
} else {
|
|
"none"
|
|
};
|
|
|
|
Ok(json!({
|
|
"status": status,
|
|
"analysis": {
|
|
"is_none": analysis.is_none,
|
|
"is_normal": analysis.is_normal,
|
|
"is_up_to_date": analysis.is_up_to_date,
|
|
"is_fast_forward": analysis.is_fast_forward,
|
|
"is_unborn": analysis.is_unborn,
|
|
},
|
|
"preference": {
|
|
"is_none": pref.is_none,
|
|
"is_no_fast_forward": pref.is_no_fast_forward,
|
|
"is_fastforward_only": pref.is_fastforward_only,
|
|
},
|
|
}))
|
|
}
|
|
}
|
|
|
|
pub struct GitMergeIsConflictedTool;
|
|
|
|
impl GitMergeIsConflictedTool {
|
|
pub fn new() -> Self {
|
|
Self
|
|
}
|
|
}
|
|
|
|
impl Default for GitMergeIsConflictedTool {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl FunctionCall for GitMergeIsConflictedTool {
|
|
type Context = AppAgentContext;
|
|
|
|
fn name(&self) -> &'static str {
|
|
"git_merge_is_conflicted"
|
|
}
|
|
|
|
fn description(&self) -> &'static str {
|
|
"Check if the repository is currently in a conflicted merge state."
|
|
}
|
|
|
|
fn schema(&self) -> Value {
|
|
json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"workspace": { "type": "string", "description": "Workspace name" },
|
|
"repo": { "type": "string", "description": "Repository name" }
|
|
},
|
|
"required": ["workspace", "repo"]
|
|
})
|
|
}
|
|
|
|
async fn call(
|
|
&self,
|
|
ctx: &mut AppAgentContext,
|
|
args: Value,
|
|
) -> AiResult<Value> {
|
|
let git = git_ctx(ctx)?;
|
|
let workspace = arg_str(&args, "workspace")?;
|
|
let repo_name = arg_str(&args, "repo")?;
|
|
|
|
let repo =
|
|
require_repo_member(git, ctx.user_id, workspace, repo_name).await?;
|
|
|
|
let mut client = MergeServiceClient::new(git.channel.clone());
|
|
let resp = client
|
|
.merge_is_conflicted(p::MergeIsConflictedRequest {
|
|
repo_id: repo.id.to_string(),
|
|
})
|
|
.await
|
|
.map_err(rpc_err)?
|
|
.into_inner();
|
|
|
|
Ok(json!({ "is_conflicted": resp.is_conflicted }))
|
|
}
|
|
}
|