//! Every span the unified builder opens matches what the replaced completion
//! and modality builders opened, captured before they were removed: name,
//! target, parent, declared fields, and recorded values, case by case.
use serde_json::{Value, json};
use super::*;
use crate::completion::CompletionRequest;
use crate::driver::{Exchange, Local, Model, Opened, Opening, Transport};
use crate::embeddings::EmbeddingResponse;
use crate::error::{EncodeError, ProviderError};
use crate::operation::{Completion, Embedding, Finish, Rerank, RerankRequest, Transcription};
use crate::rerank::RerankResponse;
use crate::test_utils::{CapturedSpan, TraceCapture};
use crate::transcription::{TranscriptionRequest, TranscriptionResponse};
use crate::wire::{Decoder, Descriptor, Flow, Fold, Mode, Out, Wire, WireEvent};
use futures::StreamExt;
/// One JSON line per case, in the order [`cases`] runs them.
const EXPECTED: &str = r#"{"case":"fresh completion chat","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model"}}]}
{"case":"fresh completion chat + system recorded","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]"}}]}
{"case":"adopted completion chat","spans":[{"name":"agent_chat","target":"runtime","parent":null,"fields":["rig.completion_parent","gen_ai.operation.name","gen_ai.system_instructions","gen_ai.provider.name","gen_ai.request.model","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"rig.completion_parent":true,"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]","gen_ai.usage.input_tokens":10,"gen_ai.usage.output_tokens":0,"gen_ai.usage.reasoning_tokens":3}}]}
{"case":"fresh completion chat streamed","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.request.stream":true}}]}
{"case":"fresh completion chat streamed + system recorded","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]","gen_ai.request.stream":true}}]}
{"case":"adopted completion chat streamed","spans":[{"name":"agent_chat","target":"runtime","parent":null,"fields":["rig.completion_parent","gen_ai.operation.name","gen_ai.system_instructions","gen_ai.provider.name","gen_ai.request.model","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"rig.completion_parent":true,"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.request.stream":true,"gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]","gen_ai.usage.input_tokens":10,"gen_ai.usage.output_tokens":0,"gen_ai.usage.reasoning_tokens":3}}]}
{"case":"fresh completion generate_content","spans":[{"name":"generate_content","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"generate_content","gen_ai.provider.name":"prov","gen_ai.request.model":"model"}}]}
{"case":"fresh completion generate_content + system recorded","spans":[{"name":"generate_content","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"generate_content","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]"}}]}
{"case":"adopted completion generate_content","spans":[{"name":"agent_chat","target":"runtime","parent":null,"fields":["rig.completion_parent","gen_ai.operation.name","gen_ai.system_instructions","gen_ai.provider.name","gen_ai.request.model","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"rig.completion_parent":true,"gen_ai.operation.name":"generate_content","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]","gen_ai.usage.input_tokens":10,"gen_ai.usage.output_tokens":0,"gen_ai.usage.reasoning_tokens":3}}]}
{"case":"fresh completion generate_content streamed","spans":[{"name":"generate_content","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"generate_content","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.request.stream":true}}]}
{"case":"fresh completion generate_content streamed + system recorded","spans":[{"name":"generate_content","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"generate_content","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]","gen_ai.request.stream":true}}]}
{"case":"adopted completion generate_content streamed","spans":[{"name":"agent_chat","target":"runtime","parent":null,"fields":["rig.completion_parent","gen_ai.operation.name","gen_ai.system_instructions","gen_ai.provider.name","gen_ai.request.model","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"rig.completion_parent":true,"gen_ai.operation.name":"generate_content","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.request.stream":true,"gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]","gen_ai.usage.input_tokens":10,"gen_ai.usage.output_tokens":0,"gen_ai.usage.reasoning_tokens":3}}]}
{"case":"fresh completion deprecated chat_streaming","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.request.stream":true}}]}
{"case":"fresh completion deprecated chat_streaming + system recorded","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]","gen_ai.request.stream":true}}]}
{"case":"adopted completion deprecated chat_streaming","spans":[{"name":"agent_chat","target":"runtime","parent":null,"fields":["rig.completion_parent","gen_ai.operation.name","gen_ai.system_instructions","gen_ai.provider.name","gen_ai.request.model","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"rig.completion_parent":true,"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.request.stream":true,"gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]","gen_ai.usage.input_tokens":10,"gen_ai.usage.output_tokens":0,"gen_ai.usage.reasoning_tokens":3}}]}
{"case":"fresh completion deprecated interactions","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model"}}]}
{"case":"fresh completion deprecated interactions + system recorded","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]"}}]}
{"case":"adopted completion deprecated interactions","spans":[{"name":"agent_chat","target":"runtime","parent":null,"fields":["rig.completion_parent","gen_ai.operation.name","gen_ai.system_instructions","gen_ai.provider.name","gen_ai.request.model","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"rig.completion_parent":true,"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]","gen_ai.usage.input_tokens":10,"gen_ai.usage.output_tokens":0,"gen_ai.usage.reasoning_tokens":3}}]}
{"case":"fresh completion deprecated interactions_streaming","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.request.stream":true}}]}
{"case":"fresh completion deprecated interactions_streaming + system recorded","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]","gen_ai.request.stream":true}}]}
{"case":"adopted completion deprecated interactions_streaming","spans":[{"name":"agent_chat","target":"runtime","parent":null,"fields":["rig.completion_parent","gen_ai.operation.name","gen_ai.system_instructions","gen_ai.provider.name","gen_ai.request.model","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"rig.completion_parent":true,"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.request.stream":true,"gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]","gen_ai.usage.input_tokens":10,"gen_ai.usage.output_tokens":0,"gen_ai.usage.reasoning_tokens":3}}]}
{"case":"fresh completion under an ordinary ambient span is its child","spans":[{"name":"ambient","target":"runtime","parent":null,"fields":[],"values":{}},{"name":"chat","target":"rig::completions","parent":"ambient","fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model"}}]}
{"case":"fresh completion chat + system not recorded","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model"}}]}
{"case":"modality embeddings","spans":[{"name":"embeddings","target":"rig::modalities","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens"],"values":{"gen_ai.operation.name":"embeddings","gen_ai.provider.name":"prov","gen_ai.request.model":"model"}}]}
{"case":"modality rerank","spans":[{"name":"rerank","target":"rig::modalities","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens"],"values":{"gen_ai.operation.name":"rerank","gen_ai.provider.name":"prov","gen_ai.request.model":"model"}}]}
{"case":"modality transcription","spans":[{"name":"transcription","target":"rig::modalities","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens"],"values":{"gen_ai.operation.name":"transcription","gen_ai.provider.name":"prov","gen_ai.request.model":"model"}}]}
{"case":"modality image_generation","spans":[{"name":"image_generation","target":"rig::modalities","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens"],"values":{"gen_ai.operation.name":"image_generation","gen_ai.provider.name":"prov","gen_ai.request.model":"model"}}]}
{"case":"modality audio_generation","spans":[{"name":"audio_generation","target":"rig::modalities","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens"],"values":{"gen_ai.operation.name":"audio_generation","gen_ai.provider.name":"prov","gen_ai.request.model":"model"}}]}
{"case":"modality span under a completion parent stays fresh","spans":[{"name":"agent_chat","target":"runtime","parent":null,"fields":["rig.completion_parent","gen_ai.operation.name","gen_ai.system_instructions","gen_ai.provider.name","gen_ai.request.model","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"rig.completion_parent":true,"gen_ai.operation.name":"chat"}},{"name":"embeddings","target":"rig::modalities","parent":"agent_chat","fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens"],"values":{"gen_ai.operation.name":"embeddings","gen_ai.provider.name":"prov","gen_ai.request.model":"model"}}]}
{"case":"completion operation span+record","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.system_instructions":"[{\"type\":\"text\",\"content\":\"sys\"}]","gen_ai.request.stream":false,"gen_ai.response.model":"resp_model","gen_ai.usage.input_tokens":10,"gen_ai.usage.output_tokens":0,"gen_ai.usage.reasoning_tokens":3}}]}
{"case":"completion operation streaming span+record_event","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"override","gen_ai.request.stream":true,"gen_ai.response.id":"resp_1","gen_ai.response.model":"m2","gen_ai.usage.input_tokens":10,"gen_ai.usage.output_tokens":0,"gen_ai.usage.reasoning_tokens":3}}]}
{"case":"embedding operation span+record","spans":[{"name":"embeddings","target":"rig::modalities","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens"],"values":{"gen_ai.operation.name":"embeddings","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.response.id":"emb_id","gen_ai.response.model":"emb_model","gen_ai.usage.input_tokens":10,"gen_ai.usage.output_tokens":0,"gen_ai.usage.reasoning_tokens":3}}]}
{"case":"rerank operation span+record","spans":[{"name":"rerank","target":"rig::modalities","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens"],"values":{"gen_ai.operation.name":"rerank","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.response.id":"rr_id","gen_ai.response.model":"rr_model","gen_ai.usage.input_tokens":10,"gen_ai.usage.output_tokens":0,"gen_ai.usage.reasoning_tokens":3}}]}
{"case":"transcription operation span+record","spans":[{"name":"transcription","target":"rig::modalities","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens"],"values":{"gen_ai.operation.name":"transcription","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.response.id":"tr_id","gen_ai.usage.input_tokens":10,"gen_ai.usage.output_tokens":0,"gen_ai.usage.reasoning_tokens":3}}]}
{"case":"span combinator on a native response","spans":[{"name":"chat","target":"rig::completions","parent":null,"fields":["gen_ai.operation.name","gen_ai.provider.name","gen_ai.request.model","gen_ai.system_instructions","gen_ai.request.stream","gen_ai.response.id","gen_ai.response.model","rig.provider_request_id","gen_ai.usage.input_tokens","gen_ai.usage.output_tokens","gen_ai.usage.cache_read.input_tokens","gen_ai.usage.cache_creation.input_tokens","gen_ai.usage.tool_use_prompt_tokens","gen_ai.usage.reasoning_tokens","gen_ai.input.messages","gen_ai.output.messages"],"values":{"gen_ai.operation.name":"chat","gen_ai.provider.name":"prov","gen_ai.request.model":"model","gen_ai.response.id":"native_id","gen_ai.response.model":"native_model","gen_ai.usage.input_tokens":10,"gen_ai.usage.output_tokens":0,"gen_ai.usage.reasoning_tokens":3}}]}"#;
fn usage() -> Usage {
Usage {
input_tokens: Some(10),
output_tokens: Some(0),
reasoning_tokens: Some(3),
..Usage::default()
}
}
fn run(case: &str, out: &mut Vec<Value>, body: impl FnOnce()) {
let capture = TraceCapture::default();
tracing::subscriber::with_default(capture.subscriber(), body);
let spans: Vec<Value> = capture.spans().iter().map(CapturedSpan::summary).collect();
out.push(json!({ "case": case, "spans": spans }));
}
fn completion_parent() -> tracing::Span {
crate::completion_parent_span!(
target: "runtime",
name: "agent_chat",
operation: "chat",
system_instructions: Option::<&str>::None,
)
}
fn cases() -> Vec<Value> {
let mut out = Vec::new();
// The deprecated streaming and Interactions variants stay pinned: a
// downstream wire naming one reports the standard operation it maps to.
#[allow(deprecated)]
let completions = [
("chat", GenAiOperation::Chat, None),
("chat streamed", GenAiOperation::Chat, Some(true)),
("generate_content", GenAiOperation::GenerateContent, None),
(
"generate_content streamed",
GenAiOperation::GenerateContent,
Some(true),
),
(
"deprecated chat_streaming",
GenAiOperation::ChatStreaming,
None,
),
(
"deprecated interactions",
GenAiOperation::Interactions,
None,
),
(
"deprecated interactions_streaming",
GenAiOperation::InteractionsStreaming,
None,
),
];
for (name, operation, streaming) in completions {
let builder = || {
let builder = SpanBuilder::new("prov", "model", operation);
match streaming {
Some(streaming) => builder.streaming(streaming),
None => builder,
}
};
run(&format!("fresh completion {name}"), &mut out, || {
builder().build();
});
run(
&format!("fresh completion {name} + system recorded"),
&mut out,
|| {
builder().system_instructions(Some("sys"), true).build();
},
);
run(&format!("adopted completion {name}"), &mut out, || {
let parent = completion_parent();
let _entered = parent.enter();
builder()
.system_instructions(Some("sys"), true)
.build()
.record_token_usage(&usage());
});
}
run(
"fresh completion under an ordinary ambient span is its child",
&mut out,
|| {
let ambient = tracing::info_span!(target: "runtime", "ambient");
let _entered = ambient.enter();
SpanBuilder::new("prov", "model", GenAiOperation::Chat).build();
},
);
run(
"fresh completion chat + system not recorded",
&mut out,
|| {
SpanBuilder::new("prov", "model", GenAiOperation::Chat)
.system_instructions(Some("sys"), false)
.build();
},
);
let modalities = [
("embeddings", GenAiOperation::Embeddings),
("rerank", GenAiOperation::Rerank),
("transcription", GenAiOperation::Transcription),
("image_generation", GenAiOperation::ImageGeneration),
("audio_generation", GenAiOperation::AudioGeneration),
];
for (name, operation) in modalities {
run(&format!("modality {name}"), &mut out, || {
SpanBuilder::new("prov", "model", operation).build();
});
}
run(
"modality span under a completion parent stays fresh",
&mut out,
|| {
let parent = completion_parent();
let _entered = parent.enter();
SpanBuilder::new("prov", "model", GenAiOperation::Embeddings).build();
},
);
run("completion operation span+record", &mut out, || {
let request = CompletionRequest::new("hi")
.preamble("sys")
.record_content_telemetry(true);
let end = Finish {
usage: usage(),
model: Some("resp_model".into()),
..Finish::default()
};
let fold = crate::test_utils::fold_for(&request, &Scripted(end.clone()), Mode::Unary);
fold.finish(end, reply())
.expect("the fold records its response");
});
run(
"completion operation streaming span+record_event",
&mut out,
|| {
let request = CompletionRequest::new("hi").model("override");
let end = Finish {
usage: usage(),
response_id: Some("resp_1".into()),
model: Some("m2".into()),
..Finish::default()
};
// The streamed call's span records the response it finishes with.
let mut stream = Model::new(Scripted(end.clone()), Scripted(end))
.stream(request)
.expect("a scripted stream opens");
futures::executor::block_on(async {
while stream.next().await.is_some() {}
stream.finish().await.expect("the scripted reply ends")
});
},
);
run("embedding operation span+record", &mut out, || {
let wire = Local::<Embedding>::new("prov").with_id("model");
let fold = crate::test_utils::fold_for(&Vec::new(), &wire, Mode::Unary);
let response = EmbeddingResponse {
provider: "prov".into(),
response_id: Some("emb_id".into()),
model: Some("emb_model".into()),
usage: usage(),
..EmbeddingResponse::new(vec![])
};
fold.finish(response, reply())
.expect("the fold records its response");
});
run("rerank operation span+record", &mut out, || {
let request = RerankRequest {
query: "q".into(),
documents: vec!["d".into()],
};
let wire = Local::<Rerank>::new("prov").with_id("model");
let fold = crate::test_utils::fold_for(&request, &wire, Mode::Unary);
let response = RerankResponse {
provider: "prov".into(),
response_id: Some("rr_id".into()),
model: Some("rr_model".into()),
usage: usage(),
..RerankResponse::new(vec![])
};
fold.finish(response, reply())
.expect("the fold records its response");
});
run("transcription operation span+record", &mut out, || {
let request = TranscriptionRequest {
data: vec![1],
filename: "a.wav".into(),
language: None,
prompt: None,
temperature: None,
additional_params: None,
};
let wire = Local::<Transcription>::new("prov").with_id("model");
let fold = crate::test_utils::fold_for(&request, &wire, Mode::Unary);
let response = TranscriptionResponse {
provider: "prov".into(),
response_id: Some("tr_id".into()),
usage: usage(),
..TranscriptionResponse::new("text")
};
fold.finish(response, reply())
.expect("the fold records its response");
});
run("span combinator on a native response", &mut out, || {
SpanBuilder::new("prov", "model", GenAiOperation::Chat)
.build()
.record_response(Some("native_id"), Some("native_model"), &usage());
});
out
}
/// A reply that carried nothing beyond its events.
fn reply() -> crate::wire::Reply {
crate::wire::Reply {
provider: "prov".to_owned(),
raw: Value::Null,
provider_request_id: None,
}
}
#[test]
fn spans_match_the_replaced_builders() {
let _isolation = crate::test_utils::scoped_tracing_subscriber_guard_blocking();
let actual = cases();
let expected: Vec<Value> = EXPECTED
.lines()
.map(|line| serde_json::from_str(line).expect("expected case"))
.collect();
assert_eq!(actual.len(), expected.len());
for (actual, expected) in actual.iter().zip(&expected) {
assert_eq!(actual, expected, "{}", expected["case"]);
}
}
/// A completion wire named `prov` for `model`, whose transport answers with
/// one scripted end.
#[derive(Clone, Debug)]
struct Scripted(Finish);
impl crate::completion::ReplayTarget for Scripted {
fn map_options(
&self,
_request: &crate::completion::CompletionRequest,
fields: crate::completion::options::OptionFields<'_>,
) -> crate::completion::options::OptionMap {
crate::test_utils::refuse_options(fields)
}
fn api(&self) -> crate::message::Api {
crate::message::Api::from_static("prov.chat")
}
fn provider(&self) -> &str {
"prov"
}
fn model(&self) -> &str {
"model"
}
fn accepts(&self, _model: &str) -> crate::completion::Accepts {
crate::completion::Accepts::ALL
}
}
/// The scripted vendor's reply document: the model its end names.
#[derive(Default)]
struct ScriptedDocument(Value);
impl crate::wire::document::Serves<Completion> for ScriptedDocument {}
impl crate::wire::document::Reassemble<Finish> for ScriptedDocument {
fn absorb(&mut self, end: &Finish) {
self.0 = serde_json::json!({ "model": end.model });
}
fn finish(self) -> Value {
self.0
}
}
impl Wire for Scripted {
type Op = Completion;
type Payload = ();
type Frame = Finish;
type Decoder<'id> = Ends;
type Reassembler = ScriptedDocument;
fn describe(&self) -> Descriptor<'_> {
Descriptor::new("prov").model("model").replay(self)
}
fn encode(&self, _request: CompletionRequest, _mode: Mode) -> Result<(), EncodeError> {
Ok(())
}
fn decoder<'id>(&self) -> Self::Decoder<'id> {
Ends
}
}
impl Transport<Scripted> for Scripted {
fn send(&self, _payload: (), _exchange: Exchange) -> Opening<Finish> {
Opening::ready(Opened::new(futures::stream::iter([Ok(self.0.clone())])))
}
}
/// Ends the reply with the scripted end.
struct Ends;
impl<'id> Decoder<'id, Completion, Finish> for Ends {
type Event = Finish;
fn classify(&self, frame: Finish) -> WireEvent<Finish> {
WireEvent::Known(frame)
}
fn decode(&mut self, end: Finish, out: Out<'id, Completion>) -> Result<Flow, ProviderError> {
Ok(out.end(end))
}
}