Skip to main content

simple_agent_type/
telemetry.rs

1//! Telemetry and tracing configuration types for OpenTelemetry integration.
2
3use serde::{Deserialize, Serialize};
4
5/// Which API format to use when communicating with a provider.
6#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
7pub enum ApiFormat {
8    /// OpenAI Chat Completions API format.
9    #[default]
10    ChatCompletions,
11    /// OpenAI Responses API format.
12    Responses,
13}
14
15/// The OTLP export protocol to use.
16#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
17pub enum OtelProtocol {
18    /// gRPC protocol.
19    #[default]
20    Grpc,
21    /// HTTP/protobuf protocol.
22    HttpProtobuf,
23}
24
25/// Configuration for OpenTelemetry telemetry export.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct TelemetryConfig {
28    /// Whether telemetry collection is enabled.
29    pub enabled: bool,
30    /// OTLP collector endpoint URL.
31    pub endpoint: String,
32    /// OTLP export protocol (gRPC or HTTP/protobuf).
33    pub protocol: OtelProtocol,
34    /// Additional headers sent with OTLP exports.
35    pub headers: Vec<(String, String)>,
36    /// The `service.name` resource attribute.
37    pub service_name: String,
38}
39
40impl Default for TelemetryConfig {
41    fn default() -> Self {
42        Self {
43            enabled: false,
44            endpoint: "http://localhost:4317".into(),
45            protocol: OtelProtocol::Grpc,
46            headers: vec![],
47            service_name: "simple-agents".into(),
48        }
49    }
50}
51
52impl TelemetryConfig {
53    /// Build a [`TelemetryConfig`] from standard OpenTelemetry environment variables.
54    pub fn from_env() -> Self {
55        let enabled = std::env::var("SIMPLE_AGENTS_TRACING_ENABLED")
56            .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
57            .unwrap_or(false);
58        let endpoint = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT")
59            .unwrap_or_else(|_| "http://localhost:4317".into());
60        let protocol = match std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL").as_deref() {
61            Ok("http/protobuf") => OtelProtocol::HttpProtobuf,
62            _ => OtelProtocol::Grpc,
63        };
64        let headers = std::env::var("OTEL_EXPORTER_OTLP_HEADERS")
65            .map(|h| {
66                h.split(',')
67                    .filter_map(|pair| {
68                        let (k, v) = pair.split_once('=')?;
69                        Some((k.trim().to_string(), v.trim().to_string()))
70                    })
71                    .collect()
72            })
73            .unwrap_or_default();
74        let service_name =
75            std::env::var("OTEL_SERVICE_NAME").unwrap_or_else(|_| "simple-agents".into());
76        Self {
77            enabled,
78            endpoint,
79            protocol,
80            headers,
81            service_name,
82        }
83    }
84}
85
86/// Distributed trace context propagated through agent operations.
87#[derive(Debug, Clone, Default, Serialize, Deserialize)]
88pub struct TraceContext {
89    /// W3C Trace ID.
90    pub trace_id: Option<String>,
91    /// W3C Span ID.
92    pub span_id: Option<String>,
93    /// Logical session identifier.
94    pub session_id: Option<String>,
95    /// User identifier for attribution.
96    pub user_id: Option<String>,
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_api_format_default() {
105        assert_eq!(ApiFormat::default(), ApiFormat::ChatCompletions);
106    }
107
108    #[test]
109    fn test_telemetry_config_default() {
110        let cfg = TelemetryConfig::default();
111        assert!(!cfg.enabled);
112        assert_eq!(cfg.endpoint, "http://localhost:4317");
113    }
114
115    #[test]
116    fn test_telemetry_config_serialization() {
117        let cfg = TelemetryConfig::default();
118        let json = serde_json::to_string(&cfg).unwrap();
119        let parsed: TelemetryConfig = serde_json::from_str(&json).unwrap();
120        assert_eq!(parsed.endpoint, cfg.endpoint);
121    }
122}