gitdataai/lib/service/agent/git_tools/merge.rs

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 }))
}
}