Skip to main content

envoy/atheneum_bridge/
sessions.rs

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
42/// PATCH /atheneum/sessions/{id} — end a session
43pub 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
68/// POST /atheneum/prompts — record a prompt
69pub 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
96/// POST /atheneum/tool-calls — record a tool call
97pub 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
125/// POST /atheneum/file-writes — record a file write
126pub 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
151/// POST /atheneum/commits — record a commit
152pub 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
177/// POST /atheneum/test-runs — record a test run
178pub 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
201/// POST /atheneum/fix-chains — record a fix chain
202pub 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
224/// POST /atheneum/bench-runs — record a benchmark run
225pub 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
245/// POST /atheneum/events — record a generic event
246pub 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
265/// GET /atheneum/events — query the event log
266pub 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
284/// GET /atheneum/sessions — query recent sessions for a project
285pub 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
303/// POST /atheneum/sessions/{id}/handover — record subagent handover note
304pub 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}