mj_controller/
github_item_verdict.rs1use anyhow::{Context, Result};
4use mj_core::github_item::{GithubItemEvidence, GithubItemVerdict, MAX_BODY_BYTES};
5use serde_json::json;
6
7const DIRECT_ENDPOINT: &str = "https://api.typesafe.ai/v1/systemone";
8const HOSTED_ENDPOINT: &str =
9 "https://mj-jev-proxy.eng-admin-a63.workers.dev/v1/github-item-verdict";
10
11pub async fn classify(evidence: &GithubItemEvidence) -> Result<GithubItemVerdict> {
12 let evidence = evidence.bounded_for_request()?;
13 let key = tokio::task::spawn_blocking(mj_core::activity::verdict::api_key)
14 .await
15 .context("resolve Jev key")?;
16 let (endpoint, source, request_body) = if key.is_some() {
17 (
18 DIRECT_ENDPOINT,
19 "direct",
20 serde_json::to_vec(&evidence.upstream_body())?,
21 )
22 } else {
23 (HOSTED_ENDPOINT, "hosted", serde_json::to_vec(&evidence)?)
24 };
25
26 let attempt = match mj_core::jev::DecisionLog::open(mj_core::jev::controller_log_dir()) {
27 Ok(log) => Some(log.start(
28 &format!("github:{}#{}", evidence.item.repo, evidence.item.number),
29 "github-item-verdict",
30 "Is this GitHub item relevant to this session, and did this agent create it?",
31 &format!(
32 "{} #{}; recent session turns",
33 evidence.item.repo, evidence.item.number
34 ),
35 )),
36 Err(error) => {
37 tracing::warn!(%error, "Jev diagnostic log unavailable for GitHub item classification");
38 None
39 }
40 };
41 if let Some(attempt) = &attempt {
42 let request = serde_json::from_slice::<serde_json::Value>(&request_body)?;
43 attempt.update(
44 None,
45 json!({
46 "request": request,
47 "source": source,
48 "contract": "github-item-verdict-v1",
49 "questions": mj_core::github_item::questions(),
50 "model": "jev-latest"
51 }),
52 );
53 }
54
55 let response = crate::jev_transport::post_bounded_json(
56 endpoint,
57 key.as_deref(),
58 request_body,
59 MAX_BODY_BYTES,
60 "request GitHub item verdict",
61 )
62 .await;
63 let result = response.and_then(|body| GithubItemVerdict::parse(&body));
64 if let Some(attempt) = attempt {
65 match &result {
66 Ok(verdict) => {
67 let answer = format!(
68 "Interested: {}; created: {}.",
69 verdict.interested, verdict.created
70 );
71 attempt.update(Some(&answer), json!({"answer": answer}));
72 attempt.finish(
73 "classified",
74 "The GitHub item was classified for this session.",
75 );
76 }
77 Err(error) => {
78 attempt.update(
79 Some("Jev did not return a usable GitHub item verdict."),
80 json!({"error": format!("{error:#}")}),
81 );
82 attempt.finish(
83 "failed",
84 "GitHub item classification failed; no interest or watch decision was applied.",
85 );
86 }
87 }
88 }
89 result
90}