use galaxyui::{AppContext, SingletonEntity}; use serde::Serialize; use super::conversation::AIConversationId; use super::{ AIAgentCitation, AIAgentExchangeId, EntrypointType, PassiveSuggestionTriggerType, ServerOutputId, }; use crate::ai::llms::LLMId; use crate::server::telemetry::AgentModeCitation as CitationForTelemetry; use crate::CloudModel; pub trait ForTelemetry { type Output; fn for_telemetry(&self, ctx: &AppContext) -> Option; } impl ForTelemetry for AIAgentCitation { type Output = CitationForTelemetry; fn for_telemetry(&self, ctx: &AppContext) -> Option { match self { Self::WarpDriveObject { uid } => { CloudModel::as_ref(ctx).get_by_uid(uid).map(|object| { CitationForTelemetry::WarpDriveObject { object_type: object.object_type(), uid: object.uid(), } }) } Self::WarpDocumentation { path } => { Some(CitationForTelemetry::WarpDocs { page: path.clone() }) } Self::WebPage { url } => Some(CitationForTelemetry::WebPage { url: url.clone() }), Self::AgentMemory { memory_store_id, memory_id, .. } => Some(CitationForTelemetry::AgentMemory { memory_store_id: memory_store_id.clone(), memory_id: memory_id.clone(), }), } } } impl EntrypointType { pub fn entrypoint(&self) -> String { match self { Self::PromptSuggestion { is_static, is_coding, } => match (is_static, is_coding) { (true, true) => "PROMPT_SUGGESTION.CODING_STATIC".to_string(), (true, false) => "PROMPT_SUGGESTION.STATIC".to_string(), (false, true) => "PROMPT_SUGGESTION.CODING".to_string(), (false, false) => "PROMPT_SUGGESTION.SIMPLE".to_string(), }, Self::ZeroStateAgentModePromptSuggestion => { "ZERO_STATE_AGENT_MODE_PROMPT_SUGGESTION".to_string() } Self::InitProjectRules => "INIT_PROJECT_RULES".to_string(), Self::UserInitiated => "USER_INITIATED".to_string(), Self::AgentInitiated => "AGENT_INITIATED".to_string(), Self::TriggerPassiveSuggestion { trigger } => { let trigger_name = match trigger { Some(PassiveSuggestionTriggerType::FilesChanged) => "FILES_CHANGED", Some(PassiveSuggestionTriggerType::CommandRun) => "COMMAND_RUN", Some(PassiveSuggestionTriggerType::ShellCommandCompleted) => { "SHELL_COMMAND_COMPLETED" } Some(PassiveSuggestionTriggerType::AgentResponseCompleted) => { "AGENT_RESPONSE_COMPLETED" } None => "NONE", }; format!("TRIGGER_SUGGEST_PROMPT.{trigger_name}") } Self::CloneRepository => "CLONE_REPOSITORY".to_string(), Self::SharedSession => "SHARED_SESSION".to_string(), Self::ResumeConversation => "RESUME_CONVERSATION".to_string(), } } } #[derive(Clone, Default, Debug, Serialize)] pub struct AIIdentifiers { /// Useful for joining to client-side telemetry. #[serde(skip_serializing_if = "Option::is_none")] pub client_conversation_id: Option, /// A stable ID to relate failures for the same underlying request. #[serde(rename = "exchange_id", skip_serializing_if = "Option::is_none")] pub client_exchange_id: Option, /// Unique ID for this output coming from the AI API. Generated by the server. Only passed in /// the initial response chunk. #[serde(skip_serializing_if = "Option::is_none")] pub server_output_id: Option, /// The conversation ID included in the response chunk. /// /// Once this is set, it is never updated. That shouldn't be an issue because the conversation /// ID is only expected to be passed in the initial response chunk. /// /// Note that this conversation ID is server-scoped; it is _not_ related to the /// `AIConversationId`, which is entirely a client-side abstraction. /// /// This is mainly used for server-side logging and analytics. #[serde(skip_serializing_if = "Option::is_none")] pub server_conversation_id: Option, /// The ID of the model actually used to generate the output. This may differ from the requested model. #[serde(skip_serializing_if = "Option::is_none")] pub model_id: Option, }