// Copyright (c) 2026 Yingjie Shang
// agent-telemetry is licensed under Mulan PSL v2.
///| Agent turn span helpers
///|
/// Start a span for a single agent turn.
///
/// Sets:
/// - `agent.turn.input`
/// - `agent.turn.max_tool_turns`
pub fn start_agent_turn_span(
tracer : @trace.Tracer,
input : String,
max_tool_turns : Int,
) -> @trace.Span {
let attributes = [
@otel.KeyValue::new("agent.turn.input", String(input)),
@otel.KeyValue::new(
"agent.turn.max_tool_turns",
Int64(max_tool_turns.to_int64()),
),
]
start_span(tracer, "agent.turn", kind=@trace.Internal, attributes~)
}
///|
/// Record turn-level attributes before ending an agent turn span.
///
/// Sets:
/// - `agent.turn.actual_turns`
/// - `agent.turn.tool_call_count`
/// - `agent.turn.output`
pub fn set_turn(
span : @trace.Span,
actual_turns : Int,
tool_call_count : Int,
output : String,
) -> Unit {
span.set_attribute(
@otel.KeyValue::new(
"agent.turn.actual_turns",
Int64(actual_turns.to_int64()),
),
)
span.set_attribute(
@otel.KeyValue::new(
"agent.turn.tool_call_count",
Int64(tool_call_count.to_int64()),
),
)
span.set_attribute(@otel.KeyValue::new("agent.turn.output", String(output)))
}
///|
/// Mark an agent turn span as failed due to reaching the maximum tool turns.
pub fn set_turn_exhausted(span : @trace.Span) -> Unit {
span.set_status(
@trace.Status::error(description=Some("max_tool_turns_reached")),
)
}
///|
/// Start a span for a GenAI agent invocation within the same process.
///
/// Follows the `gen_ai.invoke_agent.internal` semantic convention.
///
/// Sets:
/// - `gen_ai.operation.name` = "invoke_agent"
/// - `gen_ai.agent.name` (when provided)
///
/// Span name: `invoke_agent {name}` or `invoke_agent` when name is unavailable.
pub fn start_invoke_agent_span(
tracer : @trace.Tracer,
/// The human-readable name of the invoked GenAI agent.
agent_name? : String? = None,
/// The parent span context to continue an existing trace.
parent_context? : @context.Context = @context.Context::empty(),
) -> @trace.Span {
let span_name = match agent_name {
Some(name) => "invoke_agent " + name
None => "invoke_agent"
}
let attributes : Array[@common.KeyValue] = [
@otel.KeyValue::new("gen_ai.operation.name", String("invoke_agent")),
]
match agent_name {
Some(name) =>
attributes.push(@otel.KeyValue::new("gen_ai.agent.name", String(name)))
None => ()
}
start_span(
tracer,
span_name,
kind=@trace.Internal,
attributes~,
parent_context~,
)
}
///|
/// Start a span for an agent planning or task decomposition phase.
///
/// Follows the `gen_ai.plan.internal` semantic convention.
///
/// Sets:
/// - `gen_ai.operation.name` = "plan"
/// - `gen_ai.agent.name` (when provided)
///
/// Span name: `plan {name}` or `plan` when name is unavailable.
pub fn start_plan_span(
tracer : @trace.Tracer,
/// The human-readable name of the agent performing the planning.
agent_name? : String? = None,
/// The parent span context to continue an existing trace.
parent_context? : @context.Context = @context.Context::empty(),
) -> @trace.Span {
let span_name = match agent_name {
Some(name) => "plan " + name
None => "plan"
}
let attributes : Array[@common.KeyValue] = [
@otel.KeyValue::new("gen_ai.operation.name", String("plan")),
]
match agent_name {
Some(name) =>
attributes.push(@otel.KeyValue::new("gen_ai.agent.name", String(name)))
None => ()
}
start_span(
tracer,
span_name,
kind=@trace.Internal,
attributes~,
parent_context~,
)
}