pub const CONTENT_TYPE: &str = "agdx.ct";
pub const SCHEMA_ID: &str = "agdx.sid";
pub const LOGICAL_SCHEMA_FINGERPRINT: &str = "agdx.sfp";
pub const IDX_PREFIX: &str = "agdx.idx.";
pub const INLINE_PAYLOAD: &str = "agdx.inline";
pub const PROJECTION_REF: &str = "agdx.ref";
pub const CORRELATION_ID: &str = "agdx.corr";
pub const FIELD_MESSAGE_TYPE: &str = "message_type";
pub const FIELD_TS: &str = "ts";
pub const VECTOR_FIELD: &str = "embedding";
pub const WINDOW_START: &str = "window_start";
pub const HEADER_SOFT_CAP: usize = 1024;
pub const HEADER_FRAMING_BYTES: usize = 9;
pub const HEADER_VALUE_MAX: usize = 255;
pub const CONVERSATION_ID: &str = "gen_ai.conversation.id";
pub const CONVERSATION_FIELD: &str = "conversation_id";
pub const AGENT_ID: &str = "gen_ai.agent.id";
pub const REQUEST_MODEL: &str = "gen_ai.request.model";
pub const RESPONSE_MODEL: &str = "gen_ai.response.model";
pub const PROVIDER_NAME: &str = "gen_ai.provider.name";
pub const USAGE_INPUT_TOKENS: &str = "gen_ai.usage.input_tokens";
pub const USAGE_OUTPUT_TOKENS: &str = "gen_ai.usage.output_tokens";
pub const CAUSAL_PARENT: &str = "agdx.cause";
pub const PARENT_CONVERSATION_ID: &str = "agdx.parent_conv";
pub const ROOT_CONVERSATION_ID: &str = "agdx.root_conv";
pub const TARGET_AGENT_ID: &str = "agdx.to";
pub const DELEGATED_BY: &str = "agdx.on_behalf_of";
pub const IDEMPOTENCY_KEY: &str = "agdx.idem";
pub const DEADLINE: &str = "agdx.deadline";
pub const COST_USD: &str = "agdx.cost";
pub const FENCE: &str = "agdx.fence";
pub const AGENT_VERSION: &str = "agdx.av";
pub const MEMORY_NAMESPACE: &str = "agdx.mem.ns";
pub const MEMORY_USER: &str = "agdx.mem.user";
pub const MEMORY_APP: &str = "agdx.mem.app";
#[derive(Clone, Debug, PartialEq)]
pub enum HeaderField {
Text(String),
Uint8(u8),
Uint32(u32),
Uint64(u64),
Float64(f64),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Addressee {
Agent(crate::agent::AgentId),
Broadcast,
}
impl Addressee {
pub fn as_str(&self) -> &str {
match self {
Self::Agent(agent) => agent.as_str(),
Self::Broadcast => BROADCAST,
}
}
pub fn parse(text: &str) -> Result<Self, crate::error::InvalidError> {
if text == BROADCAST {
return Ok(Self::Broadcast);
}
text.parse()
.map(Self::Agent)
.map_err(|_| crate::error::InvalidError::new("agdx.to is not an agent id or `*`"))
}
}
pub const BROADCAST: &str = "*";
#[derive(Clone, Debug, PartialEq)]
pub struct RecordHeaders {
pub envelope: Option<(u32, crate::content::ContentType)>,
pub conversation: crate::agent::ConversationId,
pub parent: Option<crate::agent::ConversationId>,
pub root: Option<crate::agent::ConversationId>,
pub agent: Option<crate::agent::AgentId>,
pub addressee: Option<Addressee>,
pub causal_parent: Option<String>,
pub idempotency_key: Option<String>,
pub correlation: Option<String>,
pub fence: Option<u64>,
pub deadline_micros: Option<u64>,
pub input_tokens: Option<u64>,
pub output_tokens: Option<u64>,
pub cost_usd: Option<f64>,
}
impl RecordHeaders {
pub fn new(conversation: crate::agent::ConversationId) -> Self {
Self {
envelope: None,
conversation,
parent: None,
root: None,
agent: None,
addressee: None,
causal_parent: None,
idempotency_key: None,
correlation: None,
fence: None,
deadline_micros: None,
input_tokens: None,
output_tokens: None,
cost_usd: None,
}
}
pub fn for_envelope(
envelope: &crate::agent::AgentEnvelope,
content_type: crate::content::ContentType,
broadcast: bool,
) -> Self {
let mut headers = Self::new(envelope.conversation);
headers.envelope = Some((crate::codes::AGENT_OP_VERSION, content_type));
headers.parent = envelope.parent;
headers.root = envelope.root;
headers.agent = Some(envelope.source.clone());
headers.addressee = match &envelope.target {
Some(target) => Some(Addressee::Agent(target.clone())),
None if broadcast => Some(Addressee::Broadcast),
None => None,
};
headers
}
pub fn encode(&self) -> Vec<(&'static str, HeaderField)> {
let text = |value: &dyn std::fmt::Display| HeaderField::Text(value.to_string());
let mut block = Vec::new();
if let Some((version, content_type)) = self.envelope {
block.push((AGENT_VERSION, HeaderField::Uint32(version)));
block.push((CONTENT_TYPE, HeaderField::Uint8(content_type.code())));
}
block.push((CONVERSATION_ID, text(&self.conversation)));
let optional: [(&'static str, Option<HeaderField>); 12] = [
(PARENT_CONVERSATION_ID, self.parent.map(|id| text(&id))),
(ROOT_CONVERSATION_ID, self.root.map(|id| text(&id))),
(
AGENT_ID,
self.agent
.as_ref()
.map(|agent| HeaderField::Text(agent.as_str().to_owned())),
),
(
TARGET_AGENT_ID,
self.addressee
.as_ref()
.map(|to| HeaderField::Text(to.as_str().to_owned())),
),
(
CAUSAL_PARENT,
self.causal_parent.clone().map(HeaderField::Text),
),
(
IDEMPOTENCY_KEY,
self.idempotency_key.clone().map(HeaderField::Text),
),
(
CORRELATION_ID,
self.correlation.clone().map(HeaderField::Text),
),
(FENCE, self.fence.map(HeaderField::Uint64)),
(DEADLINE, self.deadline_micros.map(HeaderField::Uint64)),
(
USAGE_INPUT_TOKENS,
self.input_tokens.map(HeaderField::Uint64),
),
(
USAGE_OUTPUT_TOKENS,
self.output_tokens.map(HeaderField::Uint64),
),
(COST_USD, self.cost_usd.map(HeaderField::Float64)),
];
block.extend(
optional
.into_iter()
.filter_map(|(key, value)| value.map(|value| (key, value))),
);
block.sort_by(|left, right| left.0.cmp(right.0));
block
}
}