use std::sync::Arc;
use uuid::Uuid;
use crate::api::llm::LlmRequest;
use crate::api::runtime::EventSubscriberFn;
use crate::api::runtime::global_context;
use crate::api::runtime::{current_scope_stack, task_scope_top};
use crate::api::scope::ScopeHandle;
use crate::api::scope::ScopeType;
use crate::codec::request::AnnotatedLlmRequest;
use crate::codec::traits::LlmCodec;
use crate::error::{FlowError, Result};
use crate::json::{Json, merge_json};
use crate::shared_runtime::ensure_process_runtime_owner;
pub const DYNAMO_SESSION_ID_HEADER_KEY: &str = "x-dynamo-session-id";
pub const DYNAMO_PARENT_SESSION_ID_HEADER_KEY: &str = "x-dynamo-parent-session-id";
pub(crate) fn resolve_parent_uuid(parent: Option<&ScopeHandle>) -> Option<Uuid> {
Some(
parent
.map(|handle| handle.uuid)
.unwrap_or_else(|| task_scope_top().uuid),
)
}
pub(crate) fn snapshot_event_subscribers(
scope_local_subscribers: Vec<EventSubscriberFn>,
) -> Result<Vec<EventSubscriberFn>> {
let context = global_context();
let state = context
.read()
.map_err(|error| FlowError::Internal(error.to_string()))?;
Ok(state.collect_event_subscribers(&scope_local_subscribers))
}
pub(crate) fn ensure_runtime_owner() -> Result<()> {
ensure_process_runtime_owner()
}
pub(crate) fn resolve_agent_session_ids() -> Option<(String, Option<String>)> {
let stack = current_scope_stack();
let stack = stack.read().ok()?;
let mut agent_scopes = stack
.scopes()
.iter()
.skip(1)
.filter(|scope| matches!(scope.scope_type, ScopeType::Agent))
.rev();
let current = agent_scopes.next().map(agent_scope_id)?;
let parent = agent_scopes.next().map(agent_scope_id);
Some((current, parent))
}
fn agent_scope_id(scope: &ScopeHandle) -> String {
scope
.metadata
.as_ref()
.and_then(|metadata| {
[
"codex_subagent_session_id",
"subagent_session_id",
"subagent_id",
"session_id",
]
.into_iter()
.find_map(|key| metadata.get(key).and_then(|value| value.as_str()))
})
.unwrap_or(&scope.name)
.to_string()
}
pub(crate) fn inject_dynamo_session_ids(request: &mut LlmRequest) {
let Some((current, parent)) = resolve_agent_session_ids() else {
return;
};
request.headers.insert(
DYNAMO_SESSION_ID_HEADER_KEY.to_string(),
Json::String(current),
);
match parent {
Some(parent) => {
request.headers.insert(
DYNAMO_PARENT_SESSION_ID_HEADER_KEY.to_string(),
Json::String(parent),
);
}
None => {
request.headers.remove(DYNAMO_PARENT_SESSION_ID_HEADER_KEY);
}
}
}
pub(crate) fn metadata_with_otel_status(
metadata: Option<Json>,
status_code: &'static str,
status_message: Option<String>,
) -> Option<Json> {
let mut status = serde_json::Map::new();
status.insert(
"otel.status_code".to_string(),
Json::String(status_code.to_string()),
);
if status_code == "ERROR"
&& let Some(status_message) = status_message
{
status.insert(
"otel.status_description".to_string(),
Json::String(status_message),
);
}
let mut metadata = merge_json(metadata, Some(Json::Object(status)));
if status_code != "ERROR"
&& let Some(Json::Object(metadata)) = metadata.as_mut()
{
metadata.remove("otel.status_description");
}
metadata
}
pub(crate) fn run_request_intercepts_with_codec(
name: &str,
request: LlmRequest,
codec: Option<Arc<dyn LlmCodec>>,
) -> Result<(
LlmRequest,
Option<Arc<AnnotatedLlmRequest>>,
Vec<crate::api::event::PendingMarkSpec>,
)> {
let annotated = match &codec {
Some(codec) => Some(codec.decode(&request)?),
None => None,
};
let entries = {
let scope_stack = current_scope_stack();
let scope_guard = scope_stack.read().expect("scope stack lock poisoned");
let scope_locals = scope_guard
.collect_scope_local_registries(|registries| ®istries.llm_request_intercepts);
let context = global_context();
let state = context
.read()
.map_err(|error| FlowError::Internal(error.to_string()))?;
state.llm_request_intercept_entries(&scope_locals)
};
let outcome =
crate::api::runtime::NemoRelayContextState::llm_request_intercepts_snapshot_chain(
name,
request,
annotated,
&entries,
codec.is_some(),
)?;
let mut request = outcome.request;
inject_dynamo_session_ids(&mut request);
let pending_marks = outcome.pending_marks;
match (codec, outcome.annotated_request) {
(Some(codec), Some(annotated)) => {
let mut encoded = codec.encode(&annotated, &request)?;
encoded.headers.extend(request.headers);
Ok((encoded, Some(Arc::new(annotated)), pending_marks))
}
(_, annotated) => Ok((request, annotated.map(Arc::new), pending_marks)),
}
}
#[cfg(test)]
#[path = "../../tests/unit/shared_tests.rs"]
mod tests;