use crate::LifecycleEventKind;
use serde_json::{Value, json};
use super::{
FrameAdmissionDirective, ProtocolAdapter, RenderError, RenderRequest, RenderedHookPayload,
render_additional_context,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum CodexHookEvent {
SessionStart,
UserPromptSubmit,
PreCompact,
PostCompact,
Stop,
}
impl CodexHookEvent {
pub const ALL: &'static [Self] = &[
Self::SessionStart,
Self::UserPromptSubmit,
Self::PreCompact,
Self::PostCompact,
Self::Stop,
];
pub fn as_str(self) -> &'static str {
match self {
Self::SessionStart => "SessionStart",
Self::UserPromptSubmit => "UserPromptSubmit",
Self::PreCompact => "PreCompact",
Self::PostCompact => "PostCompact",
Self::Stop => "Stop",
}
}
}
pub fn codex_hook_event_for(event: LifecycleEventKind) -> Option<CodexHookEvent> {
match event {
LifecycleEventKind::SessionStarting | LifecycleEventKind::SessionStarted => {
Some(CodexHookEvent::SessionStart)
}
LifecycleEventKind::FrameOpening => Some(CodexHookEvent::UserPromptSubmit),
LifecycleEventKind::ContextPressureObserved => Some(CodexHookEvent::PreCompact),
LifecycleEventKind::ContextCompacted => Some(CodexHookEvent::PostCompact),
LifecycleEventKind::FrameEnding | LifecycleEventKind::FrameEnded => {
Some(CodexHookEvent::Stop)
}
LifecycleEventKind::FrameOpened
| LifecycleEventKind::SessionEnding
| LifecycleEventKind::SessionEnded
| LifecycleEventKind::SupervisorTick
| LifecycleEventKind::CapabilityDegraded
| LifecycleEventKind::ReceiptEmitted
| LifecycleEventKind::ReceiptGapDetected => None,
}
}
pub(crate) fn render(req: &RenderRequest<'_>) -> Result<RenderedHookPayload, RenderError> {
let Some(event) = codex_hook_event_for(req.event) else {
return Err(RenderError::UnsupportedEvent {
adapter: ProtocolAdapter::Codex,
event: req.event,
});
};
let body = match event {
CodexHookEvent::SessionStart => session_start_payload(req),
CodexHookEvent::UserPromptSubmit => user_prompt_submit_payload(req),
CodexHookEvent::PreCompact | CodexHookEvent::PostCompact => json!({}),
CodexHookEvent::Stop => stop_payload(req),
};
Ok(RenderedHookPayload {
hook_event_name: Some(event.as_str()),
body,
})
}
fn session_start_payload(req: &RenderRequest<'_>) -> Value {
json!({
"hookSpecificOutput": {
"hookEventName": CodexHookEvent::SessionStart.as_str(),
"additionalContext": render_additional_context(req.payloads),
}
})
}
fn user_prompt_submit_payload(req: &RenderRequest<'_>) -> Value {
let mut payload = json!({
"hookSpecificOutput": {
"hookEventName": CodexHookEvent::UserPromptSubmit.as_str(),
"additionalContext": render_additional_context(req.payloads),
}
});
if let Some(FrameAdmissionDirective::Block { reason }) = req.directive {
let obj = payload
.as_object_mut()
.expect("user_prompt_submit payload is a json object");
obj.insert("decision".to_string(), Value::String("block".to_string()));
obj.insert("reason".to_string(), Value::String(reason.clone()));
}
payload
}
fn stop_payload(req: &RenderRequest<'_>) -> Value {
if let Some(FrameAdmissionDirective::Block { reason }) = req.directive {
return json!({
"decision": "block",
"reason": reason,
});
}
json!({})
}