use anyhow::Result;
use async_trait::async_trait;
use crate::config::Config;
use crate::llm::{review_diff, ReviewResult};
use crate::providers::{PrMeta, Provider};
use crate::review::run_agentic;
pub struct ReviewContext<'a> {
pub client: &'a reqwest::Client,
pub cfg: &'a Config,
pub provider: Option<&'a Provider>,
pub local_root: Option<&'a std::path::Path>,
pub repo: &'a str,
pub meta: &'a PrMeta,
pub diff: &'a str,
pub omitted_note: Option<&'a str>,
pub structural_context: Option<&'a str>,
pub injected_rules: &'a str,
}
impl ReviewContext<'_> {
pub fn system_prompt(&self, rubric: &str) -> String {
crate::prompt::with_injected_rules(rubric, self.injected_rules)
}
}
#[async_trait]
pub trait ReviewBackend: Send + Sync {
async fn review(&self, ctx: &ReviewContext<'_>) -> Result<ReviewResult>;
async fn complete(&self, cfg: &Config, system: &str, user: &str) -> Result<String> {
let client = reqwest::Client::new();
crate::llm::chat_text(&client, cfg, system, user).await
}
}
pub struct OpenRouterBackend;
#[async_trait]
impl ReviewBackend for OpenRouterBackend {
async fn review(&self, ctx: &ReviewContext<'_>) -> Result<ReviewResult> {
let diff_only = ctx.system_prompt(crate::prompt::SYSTEM_PROMPT);
if let (true, Some(provider)) = (ctx.cfg.agentic, ctx.provider) {
match run_agentic(
provider,
ctx.client,
ctx.cfg,
ctx.meta,
ctx.diff,
ctx.omitted_note,
ctx.structural_context,
ctx.repo,
&ctx.system_prompt(crate::agent::AGENT_SYSTEM_PROMPT),
)
.await
{
Ok(r) => Ok(r),
Err(e) => {
tracing::warn!(
"agentic review failed for {}#{} ({e:#}); falling back to diff-only",
ctx.repo,
ctx.meta.pr,
);
review_diff(
ctx.client,
ctx.cfg,
ctx.meta,
ctx.diff,
ctx.omitted_note.map(str::to_string),
ctx.structural_context,
&diff_only,
)
.await
}
}
} else {
review_diff(
ctx.client,
ctx.cfg,
ctx.meta,
ctx.diff,
ctx.omitted_note.map(str::to_string),
ctx.structural_context,
&diff_only,
)
.await
}
}
}