1use axum::extract::{Path, Query, State};
2use axum::Json;
3use std::sync::Arc;
4
5use crate::atheneum_bridge::types::*;
6use crate::error::Result;
7use crate::http::AppState;
8use atheneum::graph::SessionSummary;
9
10pub async fn post_session(
11 State(state): State<Arc<AppState>>,
12 Json(req): Json<RecordSessionRequest>,
13) -> Result<impl axum::response::IntoResponse> {
14 let session_id = req.session_id.clone();
15 let session_id_for_response = session_id.clone();
16 state
17 .with_atheneum_async(move |g| {
18 g.record_session(atheneum::graph::SessionParams {
19 session_id: req.session_id,
20 agent_name: req.agent,
21 project: req.project,
22 tool: req.tool,
23 trigger: req.trigger,
24 model: req.model,
25 git_branch: req.git_branch,
26 git_head: req.git_head,
27 parent_session_id: req.parent_session_id,
28 relations: vec![],
29 })
30 .map_err(crate::error::EnvoyError::from)
31 })
32 .await?;
33 Ok((
34 axum::http::StatusCode::CREATED,
35 Json(RecordSessionResponse {
36 session_id: session_id_for_response,
37 recorded: true,
38 }),
39 ))
40}
41
42pub async fn patch_session(
44 State(state): State<Arc<AppState>>,
45 Path(session_id): Path<String>,
46 Json(req): Json<EndSessionRequest>,
47) -> Result<axum::http::StatusCode> {
48 state
49 .with_atheneum_async(move |g| {
50 g.end_session(atheneum::graph::EndSessionParams {
51 session_id,
52 exit_status: req.exit_status,
53 prompt_count: req.prompt_count as i64,
54 tool_call_count: req.tool_call_count as i64,
55 file_write_count: req.file_write_count as i64,
56 commit_count: req.commit_count as i64,
57 test_run_count: req.test_run_count as i64,
58 total_input_tokens: req.total_input_tokens as i64,
59 total_output_tokens: req.total_output_tokens as i64,
60 total_cost_usd: req.total_cost_usd,
61 })
62 .map_err(crate::error::EnvoyError::from)
63 })
64 .await?;
65 Ok(axum::http::StatusCode::OK)
66}
67
68pub async fn post_prompt(
70 State(state): State<Arc<AppState>>,
71 Json(req): Json<RecordPromptRequest>,
72) -> Result<Json<serde_json::Value>> {
73 state
74 .with_atheneum_async(move |g| {
75 g.record_evidence_prompt(atheneum::graph::PromptParams {
76 session_id: req.session_id,
77 role: req.role,
78 sequence: req.sequence as i64,
79 content_summary: None,
80 source: None,
81 input_hash: req.input_hash,
82 input_tokens: req.input_tokens.map(|v| v as i64),
83 output_hash: req.output_hash,
84 output_tokens: req.output_tokens.map(|v| v as i64),
85 latency_ms: req.latency_ms.map(|v| v as i64),
86 model: req.model,
87 cost_usd: req.cost_usd,
88 relations: vec![],
89 })
90 .map_err(crate::error::EnvoyError::from)
91 })
92 .await?;
93 Ok(Json(serde_json::json!({"recorded": true})))
94}
95
96pub async fn post_tool_call(
98 State(state): State<Arc<AppState>>,
99 Json(req): Json<RecordToolCallRequest>,
100) -> Result<Json<serde_json::Value>> {
101 state
102 .with_atheneum_async(move |g| {
103 g.record_evidence_tool_call(atheneum::graph::ToolCallParams {
104 session_id: req.session_id,
105 tool_name: req.tool_name,
106 tool_version: req.tool_version,
107 sequence: Some(0),
108 source: None,
109 input_hash: req.input_hash,
110 input_summary: req.input_summary,
111 output_hash: req.output_hash,
112 output_summary: req.output_summary,
113 exit_status: req.exit_status,
114 latency_ms: req.latency_ms as i64,
115 input_tokens_est: req.input_tokens_est.map(|v| v as i64),
116 tool_category: req.tool_category,
117 relations: vec![],
118 })
119 .map_err(crate::error::EnvoyError::from)
120 })
121 .await?;
122 Ok(Json(serde_json::json!({"recorded": true})))
123}
124
125pub async fn post_file_write(
127 State(state): State<Arc<AppState>>,
128 Json(req): Json<RecordFileWriteRequest>,
129) -> Result<Json<serde_json::Value>> {
130 state
131 .with_atheneum_async(move |g| {
132 g.record_evidence_file_write(atheneum::graph::FileWriteParams {
133 session_id: req.session_id,
134 file_path: req.file_path,
135 file_id: req.file_id,
136 sequence: Some(0),
137 before_hash: req.before_hash,
138 after_hash: req.after_hash,
139 lines_added: req.lines_added as i64,
140 lines_deleted: req.lines_deleted as i64,
141 lines_changed: req.lines_changed as i64,
142 write_type: req.write_type,
143 relations: vec![],
144 })
145 .map_err(crate::error::EnvoyError::from)
146 })
147 .await?;
148 Ok(Json(serde_json::json!({"recorded": true})))
149}
150
151pub async fn post_commit(
153 State(state): State<Arc<AppState>>,
154 Json(req): Json<RecordCommitRequest>,
155) -> Result<Json<serde_json::Value>> {
156 state
157 .with_atheneum_async(move |g| {
158 g.record_evidence_commit(atheneum::graph::CommitParams {
159 session_id: req.session_id,
160 commit_sha: req.commit_sha,
161 parent_sha: req.parent_sha,
162 message: req.message,
163 author: req.author,
164 files_changed: req.files_changed as i64,
165 lines_inserted: req.lines_inserted as i64,
166 lines_deleted: req.lines_deleted as i64,
167 commit_type: req.commit_type,
168 feature_tag: req.feature_tag,
169 relations: vec![],
170 })
171 .map_err(crate::error::EnvoyError::from)
172 })
173 .await?;
174 Ok(Json(serde_json::json!({"recorded": true})))
175}
176
177pub async fn post_test_run(
179 State(state): State<Arc<AppState>>,
180 Json(req): Json<RecordTestRunRequest>,
181) -> Result<Json<serde_json::Value>> {
182 state
183 .with_atheneum_async(move |g| {
184 g.record_evidence_test_run(atheneum::graph::TestRunParams {
185 session_id: req.session_id,
186 test_name: req.test_name,
187 test_suite: req.test_suite,
188 test_command: req.test_command,
189 result: req.result,
190 duration_ms: req.duration_ms as i64,
191 logs_summary: req.logs_summary,
192 commit_sha: req.commit_sha,
193 relations: vec![],
194 })
195 .map_err(crate::error::EnvoyError::from)
196 })
197 .await?;
198 Ok(Json(serde_json::json!({"recorded": true})))
199}
200
201pub async fn post_fix_chain(
203 State(state): State<Arc<AppState>>,
204 Json(req): Json<RecordFixChainRequest>,
205) -> Result<Json<serde_json::Value>> {
206 state
207 .with_atheneum_async(move |g| {
208 g.record_evidence_fix_chain(atheneum::graph::FixChainParams {
209 session_id: req.session_id,
210 bug_commit_sha: req.bug_commit_sha,
211 fix_commit_sha: req.fix_commit_sha,
212 fix_type: req.fix_type,
213 severity: req.severity,
214 cycles_to_fix: req.cycles_to_fix as i64,
215 time_to_fix_ms: req.time_to_fix_ms as i64,
216 relations: vec![],
217 })
218 .map_err(crate::error::EnvoyError::from)
219 })
220 .await?;
221 Ok(Json(serde_json::json!({"recorded": true})))
222}
223
224pub async fn post_bench_run(
226 State(state): State<Arc<AppState>>,
227 Json(req): Json<RecordBenchRunRequest>,
228) -> Result<Json<serde_json::Value>> {
229 state
230 .with_atheneum_async(move |g| {
231 g.record_evidence_bench_run(
232 req.session_id,
233 req.bench_name,
234 req.mean_ns,
235 req.median_ns,
236 req.p95_ns,
237 req.is_regression,
238 )
239 .map_err(crate::error::EnvoyError::from)
240 })
241 .await?;
242 Ok(Json(serde_json::json!({"recorded": true})))
243}
244
245pub async fn post_event(
247 State(state): State<Arc<AppState>>,
248 Json(req): Json<RecordEventRequest>,
249) -> Result<Json<serde_json::Value>> {
250 state
251 .with_atheneum_async(move |g| {
252 g.record_event(atheneum::graph::RecordEventParams {
253 session_id: req.session_id,
254 event_type: req.event_type,
255 entity_id: req.entity_id,
256 payload: req.payload,
257 relations: vec![],
258 })
259 .map_err(crate::error::EnvoyError::from)
260 })
261 .await?;
262 Ok(Json(serde_json::json!({"recorded": true})))
263}
264
265pub async fn get_events(
267 State(state): State<Arc<AppState>>,
268 Query(query): Query<QueryEventsQuery>,
269) -> Result<impl axum::response::IntoResponse> {
270 let session_id = query.session_id.clone();
271 let event_type = query.event_type.clone();
272 let limit = query.limit;
273
274 let events: Vec<serde_json::Value> = state
275 .with_atheneum_async(move |g| {
276 g.query_events(session_id.as_deref(), event_type.as_deref(), limit)
277 .map_err(crate::error::EnvoyError::from)
278 })
279 .await?;
280
281 Ok(Json(QueryEventsResponse { events }))
282}
283
284pub async fn get_sessions(
286 State(state): State<Arc<AppState>>,
287 Query(query): Query<QuerySessionsQuery>,
288) -> Result<impl axum::response::IntoResponse> {
289 let project = query.project.clone();
290 let last = query.last;
291 let parent_id = query.parent_id.clone();
292
293 let sessions: Vec<SessionSummary> = state
294 .with_atheneum_async(move |g| {
295 g.query_sessions(project.as_deref(), last, parent_id.as_deref())
296 .map_err(crate::error::EnvoyError::from)
297 })
298 .await?;
299
300 Ok(Json(sessions))
301}
302
303pub async fn post_subagent_handover(
305 State(state): State<Arc<AppState>>,
306 Path(session_id): Path<String>,
307 Json(req): Json<SubagentHandoverRequest>,
308) -> Result<Json<serde_json::Value>> {
309 state
310 .with_atheneum_async(move |g| {
311 g.record_subagent_handover(&session_id, &req.summary, &req.files_changed, &req.outcome)
312 .map_err(crate::error::EnvoyError::from)
313 })
314 .await?;
315 Ok(Json(serde_json::json!({"recorded": true})))
316}