@@ -1,22 +1,33 @@
|
||||
use futures_util::stream::TryStreamExt;
|
||||
use openrouter_rs::types::Tool;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio_util::io::StreamReader;
|
||||
use tracing::instrument;
|
||||
use tracing::{info, instrument, warn};
|
||||
|
||||
use crate::{
|
||||
bot::ReviewResult,
|
||||
consts::{BOT_PROCESS_MSG, MAX_DIFF_SIZE, REVIEW_PROMPT},
|
||||
consts::{BOT_PROCESS_MSG, MAX_DIFF_SIZE, REVIEW_PROMPT, SANDBOX_SYSTEM_PROMPT},
|
||||
gitea::{GiteaAPI, ReviewPayload},
|
||||
metrics,
|
||||
open_router::OpenRouterClient,
|
||||
sandbox::{Sandbox, SandboxConfig, agent},
|
||||
};
|
||||
|
||||
#[instrument(skip(gitea_api, open_router_client, http_client, review_payload))]
|
||||
#[instrument(skip(
|
||||
gitea_api,
|
||||
open_router_client,
|
||||
http_client,
|
||||
sandbox_config,
|
||||
tools,
|
||||
review_payload
|
||||
))]
|
||||
pub async fn exec_review(
|
||||
gitea_api: &GiteaAPI,
|
||||
open_router_client: &OpenRouterClient,
|
||||
http_client: &reqwest::Client,
|
||||
model: &str,
|
||||
sandbox_config: &SandboxConfig,
|
||||
tools: Vec<Tool>,
|
||||
review_payload: ReviewPayload,
|
||||
) -> anyhow::Result<()> {
|
||||
tracing::info!(
|
||||
@@ -45,10 +56,24 @@ pub async fn exec_review(
|
||||
.replace("{comment}", &review_payload.comment.body)
|
||||
.replace("{diff}", &diff_for_llm);
|
||||
|
||||
let chat_result = open_router_client.chat(&bot_request).await?;
|
||||
let mut review_result = serde_json::from_str::<ReviewResult>(&chat_result.message)?;
|
||||
let (message, cost) = if sandbox_config.enabled {
|
||||
run_sandboxed_review(
|
||||
gitea_api,
|
||||
open_router_client,
|
||||
sandbox_config,
|
||||
tools,
|
||||
&review_payload,
|
||||
&bot_request,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
let chat_result = open_router_client.chat(&bot_request).await?;
|
||||
(chat_result.message, chat_result.cost)
|
||||
};
|
||||
|
||||
review_result.cost = chat_result.cost;
|
||||
let mut review_result = serde_json::from_str::<ReviewResult>(&message)?;
|
||||
|
||||
review_result.cost = cost;
|
||||
if let Some(cost) = review_result.cost {
|
||||
metrics::openrouter_cost_usd(cost);
|
||||
}
|
||||
@@ -86,6 +111,50 @@ pub async fn exec_review(
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the review inside a sandbox container, letting the model explore the
|
||||
/// repository with tools before answering.
|
||||
async fn run_sandboxed_review(
|
||||
gitea_api: &GiteaAPI,
|
||||
open_router_client: &OpenRouterClient,
|
||||
sandbox_config: &SandboxConfig,
|
||||
tools: Vec<Tool>,
|
||||
review_payload: &ReviewPayload,
|
||||
bot_request: &str,
|
||||
) -> anyhow::Result<(String, Option<f64>)> {
|
||||
let repo_url = gitea_api.repo_clone_url(&review_payload.repository.full_name);
|
||||
|
||||
let sandbox = Sandbox::create(
|
||||
&sandbox_config.runtime,
|
||||
&repo_url,
|
||||
gitea_api.token(),
|
||||
review_payload.pull_request.number,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let result = agent::run(
|
||||
open_router_client,
|
||||
&sandbox,
|
||||
tools,
|
||||
SANDBOX_SYSTEM_PROMPT,
|
||||
bot_request,
|
||||
sandbox_config.max_iterations,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(err) = sandbox.cleanup().await {
|
||||
warn!(%err, "Failed to clean up sandbox container");
|
||||
}
|
||||
|
||||
let result = result?;
|
||||
info!(
|
||||
iterations = result.iterations,
|
||||
cost = ?result.cost,
|
||||
"Sandboxed review finished"
|
||||
);
|
||||
|
||||
Ok((result.message, result.cost))
|
||||
}
|
||||
|
||||
fn review_result_to_markdown(review_result: &ReviewResult) -> String {
|
||||
if review_result.reviews.is_empty() {
|
||||
return String::from("No issues found. ✅");
|
||||
|
||||
Reference in New Issue
Block a user