// Zero Runtime (ZRT) gRPC Protocol
//
// NOTE: the `package` and `service` names below define the gRPC wire path the
// runtime serves (e.g. /agent_runtime.v1.AgentRuntime/EventStream). They must
// stay in lockstep with the runtime — do NOT rename them here alone or the SDK
// will fail to connect. Only the file/module name is ZRT-branded.

syntax = "proto3";
package agent_runtime.v1;

service AgentRuntime {
  rpc CreateSession(SessionConfig) returns (CreateSessionResponse);
  rpc DestroySession(DestroyRequest) returns (DestroyResponse);
  rpc GetSessionInfo(SessionInfoRequest) returns (SessionInfo);
  rpc Health(HealthRequest) returns (HealthResponse);
  rpc InjectMessage(InjectMessageRequest) returns (ContextOpResult);
  rpc RemoveMessage(RemoveMessageRequest) returns (ContextOpResult);
  rpc GetContext(GetContextRequest) returns (GetContextResponse);
  rpc ClearContext(ClearContextRequest) returns (ContextOpResult);
  rpc EventStream(stream ClientEvent) returns (stream RuntimeEvent);

  rpc SipGetCallInfo(SipGetCallInfoRequest) returns (SipGetCallInfoResponse);
  rpc SipCreateRoom(SipCreateRoomRequest) returns (SipCreateRoomResponse);
  rpc SipMakeOutboundCall(SipMakeOutboundCallRequest) returns (SipMakeOutboundCallResponse);
  rpc SipSwitchCallRoom(SipSwitchCallRoomRequest) returns (SipSwitchCallRoomResponse);
  rpc SipListRoomCalls(SipListRoomCallsRequest) returns (SipListRoomCallsResponse);
  rpc SipEndCall(SipEndCallRequest) returns (SipEndCallResponse);

  rpc LlmComplete(LlmCompleteRequest) returns (LlmCompleteResponse);

  // api-server-facing: requests a session for an agent. The runtime proxies to
  // the dispatch API, which routes the job to a WebSocket-registered worker.
  rpc Dispatch(DispatchRequest) returns (DispatchResponse);

  // api-server-facing: terminates a session.
  rpc Terminate(TerminateRequest) returns (TerminateResponse);
}

// --- Session Lifecycle ---

message SessionConfig {
  PipelineConfig pipeline = 1;
  AgentConfig agent = 2;
  RoomConfig room = 3;
  CredentialsConfig credentials = 4;
  SessionLimits limits = 5;
  VersionInfo client_version = 6;
  RecordingConfig recording = 7;
  string session_id = 8;
}

message CreateSessionResponse {
  oneof result {
    SessionInfo session = 1;
    SessionRejected rejected = 2;
  }
  string session_capability = 3;
}

message SessionInfo {
  string session_id = 1;
  string state = 2;
  uint64 created_at_unix_ms = 3;
  string pipeline_mode = 4;
  string runtime_version = 5;
}

message SessionRejected {
  string reason = 1;
  uint32 current_sessions = 2;
  uint32 max_sessions = 3;
  string message = 4;
}

message DestroyRequest { string session_id = 1; string reason = 2; }
message DestroyResponse { bool success = 1; }
message SessionInfoRequest { string session_id = 1; }
message SessionLimits { uint32 inactivity_timeout_seconds = 1; uint32 max_session_duration_seconds = 2; }

// --- Pipeline Config ---

message PipelineConfig {
  string mode = 1;
  CascadeConfig cascade = 2;
  RealtimeProviderConfig realtime = 3;
}

message RealtimeProviderConfig {
  string provider = 1;
  string model = 2;
  string voice = 3;
  string realtime_mode = 4;
  repeated string response_modalities = 5;
  map<string, string> params = 10;
  GeminiLiveExtras gemini_live_extras = 11;
}

message GeminiLiveExtras {
  optional float top_p = 1;
  optional int32 top_k = 2;
  optional uint32 max_output_tokens = 3;
  optional float presence_penalty = 4;
  optional float frequency_penalty = 5;
  optional string language_code = 6;
  optional int32 thinking_budget = 7;
  optional bool include_thoughts = 8;
  GeminiVadConfig vad = 9;
  optional uint32 context_compression_trigger_tokens = 10;
  optional string session_resumption_handle = 11;
  optional bool enable_resumption = 12;
  optional bool enable_input_transcription = 13;
  optional bool enable_output_transcription = 14;
  VertexAIConfig vertex = 15;
}

message GeminiVadConfig {
  optional string start_sensitivity = 1;
  optional string end_sensitivity = 2;
  optional uint32 prefix_padding_ms = 3;
  optional uint32 silence_duration_ms = 4;
}

message CascadeConfig {
  STTProviderConfig stt = 1;
  LLMProviderConfig llm = 2;
  TTSProviderConfig tts = 3;
  VADProviderConfig vad = 4;
  InterruptConfig interrupt = 5;
  EOUConfig eou = 6;
  FillerConfig filler = 7;
  AudioConfig audio = 8;
  SentenceBufferConfig sentence_buffer = 9;
  OrchestratorTimingConfig orchestrator_timing = 10;
  VoicemailConfig voicemail = 11;
  DenoiseConfig denoise = 12;
  bool dtmf_detection_enabled = 13;
  repeated string stt_filter_patterns = 14;
  map<string, string> stt_word_substitutions = 15;
  TextProcessingConfig text_processing = 16;
  AvatarProviderConfig avatar = 17;
}

message TextProcessingConfig {
  string language = 1;
  bool verbalize_numbers = 2;
  bool verbalize_currency = 3;
  string currency_hint = 4;
}

message OrchestratorTimingConfig {
  uint32 poll_interval_ms = 1;
  uint32 max_drain_secs = 2;
  uint32 tts_drain_grace_ms = 3;
}

message InferenceConfig {
  string base_url = 1;
}

message STTProviderConfig { string provider = 1;
  string model = 2;
  string language = 3;
  uint32 endpointing_ms = 4;
  map<string, string> params = 10;
  repeated STTProviderConfig fallbacks = 11;
  optional InferenceConfig inference = 12;
  float latency_threshold_ms = 13;       // 0 = disabled; final-transcript latency ceiling for fallover
  uint32 consecutive_latency_hits = 14;  // consecutive breaches before fail-over
  uint32 temporary_disable_sec = 15;             // 0 = runtime default; failed-provider cooldown
  uint32 permanent_disable_after_attempts = 16;  // 0 = runtime default
}

message LLMProviderConfig {
  string provider = 1;
  string model = 2;
  float temperature = 3;
  uint32 max_output_tokens = 4;
  map<string, string> params = 10;
  repeated LLMProviderConfig fallbacks = 11;
  GeminiLLMExtras gemini_extras = 12;
  float latency_threshold_ms = 13;       // 0 = disabled; TTFT ceiling for fallover
  uint32 consecutive_latency_hits = 14;  // consecutive breaches before fail-over
  optional InferenceConfig inference = 15;
  uint32 temporary_disable_sec = 16;             // 0 = runtime default; failed-provider cooldown
  uint32 permanent_disable_after_attempts = 17;  // 0 = runtime default
}
message TTSProviderConfig {
  string provider = 1;
  string voice = 2;
  string model = 3;
  string language = 4;
  map<string, string> params = 10;
  repeated TTSProviderConfig fallbacks = 11;
  CartesiaExtras cartesia_extras = 12;
  float latency_threshold_ms = 13;       // 0 = disabled; TTFB ceiling for fallover
  uint32 consecutive_latency_hits = 14;  // consecutive breaches before fail-over
  optional InferenceConfig inference = 15;
  uint32 temporary_disable_sec = 16;             // 0 = runtime default; failed-provider cooldown
  uint32 permanent_disable_after_attempts = 17;  // 0 = runtime default
}

// ── Typed extras for structured provider config ─────────────────────────────

message GeminiLLMExtras {
  optional int32 thinking_budget = 1;
  optional bool include_thoughts = 2;
  repeated SafetySetting safety_settings = 3;
  VertexAIConfig vertex = 4;
}

message SafetySetting {
  string category = 1;
  string threshold = 2;
}

message VertexAIConfig {
  string project_id = 1;
  string location = 2;
  string service_account_json = 3;
  string service_account_path = 4;
}

message CartesiaExtras {
  repeated float voice_embedding = 1;
}
message VADProviderConfig {
  float threshold = 1;
  uint32 min_speech_ms = 2;
  uint32 min_silence_ms = 3;
  float stop_threshold = 4;
  float min_speech_duration = 5;
  float min_silence_duration = 6;
  float padding_duration = 7;
  float max_buffered_speech = 8;
  bool force_cpu = 9;
  float smoothing_factor = 10;
}
message InterruptConfig {
  string mode = 1;
  uint32 min_duration_ms = 2;
  uint32 min_words = 3;
  uint32 cooldown_ms = 4;
  uint32 false_interrupt_pause_duration_ms = 5;
  bool resume_on_false_interrupt = 6;
  uint32 interrupt_fade_duration_ms = 7;
}

message EOUConfig {
  float threshold = 1;
  uint32 min_wait_ms = 2;
  uint32 max_wait_ms = 3;
  string provider = 4;
  string model_id = 5;
  string host = 6;
  string auth_token = 7;
  string mode = 8;
}

message FillerConfig {
  bool filter_filler_words = 1;
  repeated string filler_words = 2;
  bool enable_verbal_fillers = 3;
  repeated string verbal_fillers = 4;
  uint32 verbal_filler_min_words = 5;
}

message AudioConfig {
  uint32 tts_sample_rate = 1;
  uint32 playback_grace_ms = 2;
}

message SentenceBufferConfig {
  uint32 min_clause_len = 1;
  uint32 first_clause_len = 2;
  uint32 min_sentence_len = 3;
}

// --- Agent Config ---

message AgentConfig {
  string instructions = 1;
  repeated ToolSchemaProto tools = 2;
  repeated string registered_hooks = 3;
  bool before_llm_hook = 4;
  ContextWindowConfig context_window = 5;
  string greeting = 6;
  bool append_voice_suffix = 7;
  string voice_suffix = 8;
  repeated MCPServerConfig mcp_servers = 9;
  KnowledgeBaseConfig knowledge_base = 10;
  bool inherit_context = 11;
  string agent_id = 12;
  bool llm_stream_hook_enabled = 13;
  uint32 llm_stream_hook_timeout_ms = 14;
  repeated NamedAgentConfig alternates = 15;
  bool greeting_non_interruptible = 16;
  bool stt_hook_enabled = 17;
  uint32 stt_hook_timeout_ms = 18;
  bool tts_hook_enabled = 19;
  uint32 tts_hook_timeout_ms = 20;
}

message NamedAgentConfig {
  string agent_id = 1;
  string instructions = 2;
  repeated ToolSchemaProto tools = 3;
  string greeting = 4;
}

message ToolSchemaProto { string name = 1;
  string description = 2;
  string parameters_json_schema = 3;
}

message ContextWindowConfig { uint32 max_tokens = 1;
  uint32 max_context_items = 2;
  uint32 keep_recent_turns = 3;
  uint32 max_tool_calls_per_turn = 4;
  // Optional dedicated LLM for context summarization. When unset, the runtime
  // summarizes with the main pipeline LLM.
  LLMProviderConfig summary_llm = 5;
}

// --- Other Config ---

message RoomConfig {
  string room_id = 1;
  string auth_token = 2;
  string agent_name = 3;
  reserved 4;
  reserved "playground";
  bool vision = 5;
  bool recording_enabled = 6;
  bool background_audio_enabled = 7;
  bool auto_end_session = 8;
  uint32 session_timeout_seconds = 9;
  uint32 no_participant_timeout_seconds = 10;
  string signaling_base_url = 11;
  bool send_logs_to_dashboard = 12;
  string dashboard_log_level = 13;
  bool audio_listener_enabled = 14;
  string agent_participant_id = 15;
  string metrics_export_url = 16;
  map<string, string> metrics_export_headers = 17;
}
message CredentialsConfig { map<string, string> provider_keys = 1; }
message VersionInfo { string sdk_version = 1; string minimum_runtime_version = 2; string proto_version = 3; }

// --- Context Manipulation ---

message InjectMessageRequest { string session_id = 1; ContextMessageProto message = 2; string position = 3; }
message RemoveMessageRequest { string session_id = 1; string message_id = 2; }
message GetContextRequest { string session_id = 1; uint32 last_n = 2; }
message GetContextResponse { repeated ContextMessageProto messages = 1; uint32 total_messages = 2; uint32 total_tokens = 3; }
message ClearContextRequest { string session_id = 1; bool keep_system_prompt = 2; }
message ContextOpResult { bool success = 1; string error = 2; uint32 total_messages = 3; uint32 total_tokens = 4; }
message ContextMessageProto { string role = 1; string content = 2; string message_id = 3; repeated ImageContentProto images = 4; repeated ToolCallProto tool_calls = 5; string tool_call_id = 6; }
message ToolCallProto { string call_id = 1; string name = 2; string arguments_json = 3; }

message ImageContentProto {
  string url = 1;
  string detail = 2;
}

// --- Health ---

message HealthRequest {}
message HealthResponse { bool healthy = 1; uint32 active_sessions = 2; uint32 max_sessions = 3; string version = 4; }

// --- Bidirectional Event Stream ---

message ClientEvent {
  string session_id = 1;
  string request_id = 2;
  oneof event {
    ToolCallResponse tool_result = 10;
    BeforeLLMResponse before_llm_result = 11;
    SayCommand say = 20;
    UpdateInstructionsCmd update_instructions = 21;
    UpdateConfigCmd update_config = 22;
    ShutdownCommand shutdown = 23;
    Keepalive keepalive = 24;
    UpdateToolsCmd update_tools = 25;
    PlayBackgroundAudioCmd play_background_audio = 26;
    StopBackgroundAudioCmd stop_background_audio = 27;
    SendDTMFCmd send_dtmf = 28;
    CallTransferCmd call_transfer = 29;
    UpdateProviderCmd update_provider = 30;
    ModifyLLMTokenCmd modify_llm_token = 31;
    PushAudioFrameCmd push_audio_frame = 32;
    RecordingStartCmd recording_start = 33;
    RecordingStopCmd recording_stop = 34;
    SendImageCmd send_image = 35;
    CancelGenerationCmd cancel_generation = 36;
    KnowledgeBaseUpsertCmd kb_upsert = 37;
    KnowledgeBaseDeleteCmd kb_delete = 38;
    GenerateCmd generate = 39;
    SendMessageWithFramesCmd send_message_with_frames = 40;
    CustomSttResult custom_stt_result = 41;
    CustomTtsAudioChunk custom_tts_audio = 42;
    PreloadBackgroundAudioCmd preload_background_audio = 43;
    SetUserInputEnabledCmd set_user_input_enabled = 44;
    PublishMessageCmd publish_message = 45;
    SubscribePubSubCmd subscribe_pubsub = 46;
    SttHookResponse stt_hook_result = 47;
    TtsHookResponse tts_hook_result = 48;
    ReconfigurePipelineCmd reconfigure_pipeline = 49;
    UpdateContextWindowCmd update_context_window = 50;
    SummarizeContextCmd summarize_context = 51;
  }
}

message RuntimeEvent {
  string session_id = 1;
  string request_id = 2;
  reserved 26, 29;
  oneof event {
    ToolCallRequest tool_call = 10;
    BeforeLLMHook before_llm = 11;
    TurnComplete turn_complete = 20;
    SayComplete say_complete = 21;
    SessionStateChange state_change = 22;
    ParticipantEventProto participant = 23;
    TranscriptEvent transcript = 24;
    AgentSpeechEvent agent_speech = 25;
    MetricsSnapshot metrics = 27;
    InterruptEvent interrupt = 28;
    ErrorEvent error = 30;
    WarningEvent warning = 31;
    DTMFEvent dtmf = 32;
    VoicemailDetectedEvent voicemail_detected = 33;
    A2AMessageEvent a2a_message = 34;
    RecordingStatusEvent recording_status = 35;
    EouDetectedEvent eou_detected = 36;
    GenerationChunkEvent generation_chunk = 37;
    AgentSwitchedEvent agent_switched = 38;
    TranscriptPreflightEvent transcript_preflight = 39;
    AgentStateChangedEvent agent_state_changed = 40;
    UserStateChangedEvent user_state_changed = 41;
    UserTurnStartEvent user_turn_start = 42;
    UserTurnEndEvent user_turn_end = 43;
    LLMTokenForReviewEvent llm_token_for_review = 44;
    VisionFrameEvent vision_frame = 45;
    AudioFrameEvent audio_frame = 46;
    StreamEventProto stream_event = 47;
    VadEventProto vad_event = 48;
    GenerationStartedEvent generation_started = 49;
    GenerationCompleteEvent generation_complete = 50;
    SynthesisStartedEvent synthesis_started = 51;
    FirstAudioByteEvent first_audio_byte = 52;
    LastAudioByteEvent last_audio_byte = 53;
    SynthesisInterruptedEvent synthesis_interrupted = 54;
    WordTimingEvent word_timing = 55;
    TtsCapabilitiesEvent tts_capabilities = 56;
    KbHitsEvent kb_hits = 57;
    SttStreamStartedEvent stt_stream_started = 58;
    SttStreamEndedEvent stt_stream_ended = 59;
    AgentTurnStartEvent agent_turn_start = 60;
    AgentTurnEndEvent agent_turn_end = 61;
    CustomSttAudioChunk custom_stt_audio = 62;
    CustomTtsSynthesize custom_tts_synthesize = 63;
    SignalingSessionAssignedEvent signaling_session_assigned = 64;
    LLMCompletedEvent llm_completed = 65;
    ComponentMetricsEvent component_metrics = 66;
    TurnMetricsEvent turn_metrics = 67;
    PubSubMessageEvent pubsub_message = 68;
    SttHookEvent stt_hook = 69;
    TtsHookEvent tts_hook = 70;
  }
}

// --- Callback Messages ---

message ToolCallRequest { string call_id = 1; string tool_name = 2; string arguments_json = 3; }
message ToolCallResponse { string call_id = 1; string result_json = 2; bool is_error = 3; }
message BeforeLLMHook { string messages_json = 1; uint32 token_count = 2; uint32 turn_number = 3; }
message BeforeLLMResponse { string modified_messages_json = 1; bool skip_turn = 2; }
message SttHookEvent { string request_id = 1; string text = 2; string language = 3; bool is_final = 4; uint32 turn_number = 5; }
message SttHookResponse { string request_id = 1; string modified_text = 2; bool drop = 3; }
message TtsHookEvent { string request_id = 1; string text = 2; string utterance_id = 3; string voice = 4; }
message TtsHookResponse { string request_id = 1; string modified_text = 2; bool drop = 3; }

// --- Notification Messages ---

message TranscriptEvent {
  string participant_id = 1;
  string text = 2;
  bool is_final = 3;
  float confidence = 4;
  string language = 5;
  uint64 start_time_us = 6;
  uint64 end_time_us = 7;
  bool turn_resumed = 8;
  string item_id = 9;
}
message TurnComplete { string user_transcript = 1; string agent_transcript = 2; uint32 tool_calls_count = 3; float total_latency_ms = 4; uint32 context_messages = 5; uint32 context_tokens = 6; }
message SessionStateChange { string state = 1; string reason = 2; }
message ParticipantEventProto { string type = 1; string participant_id = 2; string display_name = 3; }
message AgentSpeechEvent { string text = 1; bool is_final = 2; string role = 3; string item_id = 4; }
message InterruptEvent { string reason = 1; string partial_response = 2; }
message MetricsSnapshot {
  optional float stt_ttfb_ms = 1;
  optional float llm_ttfb_ms = 2;
  optional float tts_ttfb_ms = 3;
  float total_turn_latency_ms = 4;
  uint32 tokens_in = 5;
  uint32 tokens_out = 6;
  uint32 cached_tokens = 7;
  uint32 turn_number = 8;
  uint32 tokens_total = 9;
  uint32 input_text_tokens = 10;
  uint32 input_audio_tokens = 11;
  uint32 input_image_tokens = 12;
  uint32 output_text_tokens = 13;
  uint32 output_audio_tokens = 14;
  uint32 output_image_tokens = 15;
  uint32 cached_text_tokens = 16;
  uint32 cached_audio_tokens = 17;
  uint32 cached_image_tokens = 18;
  uint32 thoughts_tokens = 19;
}

// --- Command Messages ---

message SayCommand {
  string text = 1;
  bool interrupt_current = 2;
  string voice = 3;
  bool non_interruptible = 4;
  bytes audio_data = 5;
  uint32 audio_sample_rate = 6;
  bool skip_chat_context = 7;
}
message SayComplete { float duration_seconds = 1; }
message UpdateInstructionsCmd { string instructions = 1; }
message UpdateToolsCmd { repeated ToolSchemaProto tools = 1; }
message UpdateConfigCmd { string config_json = 1; }
message ShutdownCommand { string reason = 1; }
message Keepalive {}
message CancelGenerationCmd {}
message UpdateContextWindowCmd { uint32 max_tokens = 1; uint32 max_context_items = 2; uint32 keep_recent_turns = 3; }
message ReconfigurePipelineCmd {
  string pipeline_mode = 1;
  repeated UpdateProviderCmd components = 2;
}
message SummarizeContextCmd {}
message KnowledgeBaseUpsertCmd { repeated KbDocument documents = 1; }
message KnowledgeBaseDeleteCmd { repeated string ids = 1; }
message GenerateCmd {
  string text = 1;
}

// --- Error Messages ---

message ErrorEvent { string component = 1; string provider = 2; string severity = 3; string error_code = 4; string message = 5; bool is_fatal = 6; uint32 retry_attempts = 7; }
message SignalingSessionAssignedEvent { string session_id = 1; }
message WarningEvent { string code = 1; string message = 2; }
message EouDetectedEvent { float probability = 1; uint32 wait_ms = 2; string text = 3; }
message GenerationChunkEvent { string text = 1; map<string, string> metadata = 2; }
message AgentSwitchedEvent { string from = 1; string to = 2; string reason = 3; }
message TranscriptPreflightEvent { string text = 1; }
message VadEventProto {
  string kind = 1;
  uint64 timestamp_us = 2;
  float confidence = 3;
  float speech_duration_s = 4;
  float silence_duration_s = 5;
}

message GenerationStartedEvent { uint32 turn_number = 1; }
message GenerationCompleteEvent { uint32 turn_number = 1; bool was_interrupted = 2; }
message SynthesisStartedEvent { string text = 1; }
message FirstAudioByteEvent { uint32 ttfb_ms = 1; uint32 byte_count = 2; }
message LastAudioByteEvent { float duration_seconds = 1; }
message SynthesisInterruptedEvent { string reason = 1; }
message WordTimingEvent {
  string word = 1;
  float start_seconds = 2;
  float end_seconds = 3;
  string cumulative_text = 4;
}

message TtsCapabilitiesEvent {
  bool can_pause = 1;
  bool supports_word_timestamps = 2;
  uint32 sample_rate = 3;
  uint32 num_channels = 4;
}

message KbDocument {
  string id = 1;
  string content = 2;
  float score = 3;
  map<string, string> metadata = 4;
}

message KbHitsEvent {
  string query = 1;
  repeated KbDocument documents = 2;
  uint32 latency_ms = 3;
}

message ComponentMetricsEvent {
  string component = 1;
  string metrics_json = 2;
}

message TurnMetricsEvent {
  string metrics_json = 1;
}

message SttStreamStartedEvent { string provider = 1; string model = 2; }
message SttStreamEndedEvent { string provider = 1; string reason = 2; }

message AgentStateChangedEvent { string state = 1; string reason = 2; }

message UserStateChangedEvent { string state = 1; string reason = 2; }

message UserTurnStartEvent { string transcript = 1; }

message UserTurnEndEvent { string response_text = 1; bool interrupted = 2; }

message LLMCompletedEvent { string response_text = 1; bool interrupted = 2; }

message LLMTokenForReviewEvent { uint64 token_id = 1; string text = 2; }

message AgentTurnStartEvent { uint32 turn_number = 1; }

message AgentTurnEndEvent {
  uint32 turn_number = 1;
  bool interrupted = 2;
}

message CustomSttAudioChunk {
  bytes pcm = 1;
  uint32 sample_rate = 2;
  string utterance_id = 3;
  bool end_of_utterance = 4;
}

message CustomSttResult {
  string utterance_id = 1;
  string text = 2;
  bool is_final = 3;
  float confidence = 4;
  string language = 5;
  uint64 start_time_us = 6;
  uint64 end_time_us = 7;
}

message CustomTtsSynthesize {
  string text = 1;
  string utterance_id = 2;
  string voice = 3;
}

message CustomTtsAudioChunk {
  string utterance_id = 1;
  bytes pcm = 2;
  uint32 sample_rate = 3;
  bool end_of_synthesis = 4;
}

// --- Recording ---

message RecordingConfig {
  bool enabled = 1;
  bool auto_start = 2;                        // Start recording when session begins (default: true if enabled)
  RecordingFormat format = 3;                  // WAV, OGG_OPUS, MP3, FLAC
  RecordingChannelMode channel_mode = 4;       // MIXED (mono), DUAL_CHANNEL (user L, agent R)
  uint32 sample_rate = 5;                      // Output sample rate (0 = use pipeline native rate)
  uint32 bitrate_kbps = 6;                     // Encoding bitrate for lossy formats (0 = codec default)
  RecordingStorageConfig storage = 7;
  RecordingTranscriptConfig transcript = 8;
  uint32 max_duration_seconds = 9;             // Max recording length (0 = unlimited)
  uint32 max_file_size_mb = 10;                // Max file size (0 = unlimited)
  bool recording_beep = 11;                    // Play audible beep when recording starts
  bool redact_dtmf = 12;                       // Silence DTMF tones in recording (PCI compliance)
  map<string, string> custom_metadata = 13;    // User-defined key-value pairs attached to recording
  string recording_name = 14;                  // Optional human-readable name for the recording
  string recording_group = 15;                 // Logical grouping tag (e.g. campaign ID, department)
  bool apply_denoise = 16;                     // Apply noise reduction to recording output
  bool normalize_audio = 17;                   // Volume normalization
  bool trim_silence = 18;                      // Trim leading/trailing silence
  bool record_video = 19;                      // Record participant camera tracks
  bool record_screen_share = 20;               // Record screen-share video tracks
  bool record_screen_audio = 21;               // Record screen-share audio tracks (separate from mic)
}

enum RecordingFormat {
  RECORDING_FORMAT_WAV = 0;
  RECORDING_FORMAT_OGG_OPUS = 1;
  RECORDING_FORMAT_MP3 = 2;
  RECORDING_FORMAT_FLAC = 3;
}

enum RecordingChannelMode {
  RECORDING_CHANNEL_MIXED = 0;                 // Single mono track (user + agent mixed)
  RECORDING_CHANNEL_DUAL = 1;                  // Stereo: user on left, agent on right
}

message RecordingStorageConfig {
  oneof storage {
    S3StorageConfig s3 = 1;
  }
}

message S3StorageConfig {
  string bucket = 1;
  string region = 2;
  string prefix = 3;                           // Key prefix / folder path (e.g. "recordings/2024/")
  string access_key_id = 4;
  string secret_access_key = 5;
  string session_token = 6;                    // For temporary credentials (STS)
  string endpoint_url = 7;                     // Custom endpoint for S3-compatible stores (MinIO, R2, etc.)
  string storage_class = 8;                    // STANDARD, INTELLIGENT_TIERING, GLACIER, etc.
  string server_side_encryption = 9;           // AES256, aws:kms
  string kms_key_id = 10;                      // KMS key ARN (when SSE = aws:kms)
  string acl = 11;                             // private, public-read, bucket-owner-full-control, etc.
  bool multipart_upload = 12;                  // Use multipart upload for large files (default: true)
  uint32 multipart_part_size_mb = 13;          // Part size for multipart (default: 8 MB)
  uint32 upload_timeout_seconds = 14;          // Timeout for upload completion (0 = no timeout)
  uint32 max_retry_attempts = 15;              // Retry count on upload failure (default: 3)
  map<string, string> tags = 16;               // S3 object tags
  map<string, string> user_metadata = 17;      // S3 user-defined object metadata (x-amz-meta-*)
  string content_type_override = 18;           // Override Content-Type header
}

message RecordingTranscriptConfig {
  bool enabled = 1;
  RecordingTranscriptFormat format = 2;        // JSON, SRT, VTT
  bool include_word_timestamps = 3;            // Per-word timing data
  bool include_confidence = 4;                 // STT confidence scores per segment
  bool speaker_labels = 5;                     // Label segments as "user" or "agent"
  string language = 6;                         // ISO 639-1 language hint for transcript
}

enum RecordingTranscriptFormat {
  RECORDING_TRANSCRIPT_JSON = 0;
  RECORDING_TRANSCRIPT_SRT = 1;
  RECORDING_TRANSCRIPT_VTT = 2;
}

// Recording status event (runtime → SDK)
enum RecordingState {
  RECORDING_STATE_IDLE = 0;
  RECORDING_STATE_RECORDING = 1;
  RECORDING_STATE_FINALIZING = 2;
  RECORDING_STATE_UPLOADING = 3;
  RECORDING_STATE_COMPLETED = 4;
  RECORDING_STATE_FAILED = 5;
}

message RecordingStatusEvent {
  string recording_id = 1;
  RecordingState state = 2;
  string output_uri = 3;                       // Final URI (s3://bucket/key) — set on COMPLETED
  float duration_seconds = 4;                  // Current recording duration
  uint64 file_size_bytes = 5;                  // Current file size
  string error_message = 6;                    // Set on FAILED
  string transcript_uri = 7;                   // Transcript file URI — set on COMPLETED if transcript enabled
  map<string, string> metadata = 8;            // Echo back custom metadata + runtime-generated fields
  string track_kind = 9;
}

// --- Background Audio ---
message PreloadBackgroundAudioCmd {
  string file_url = 1;
  float volume = 2;
  bytes audio_data = 3;          // raw encoded audio bytes (≤16MB); preferred over file_url
}

message PlayBackgroundAudioCmd {
  string file_url = 1;
  float volume = 2;
  bool looping = 3;
  bool playback_mode = 4;
  bool is_thinking = 5;
  bytes audio_data = 6;          // raw encoded audio bytes (≤16MB); preferred over file_url
}
message StopBackgroundAudioCmd {
  bool is_thinking = 1;
}
message PushAudioFrameCmd {
  bytes pcm = 1;
  uint32 sample_rate = 2;
}

message RecordingStartCmd {
  RecordingConfig config = 1;
}

message RecordingStopCmd {}

message SendImageCmd {
  string mime_type = 1;
  bytes data = 2;
}

message MessageFrame {
  string mime_type = 1;
  bytes data = 2;
}
message SendMessageWithFramesCmd {
  string text = 1;
  repeated MessageFrame frames = 2;
  // 0 = use `frames` (bytes carried in this command). >0 = ignore `frames` and have the
  // runtime attach its own N newest decoded camera frames (cascade mode only; a no-op in
  // realtime, which already gets a live video stream). Lets the SDK request a vision turn
  // without round-tripping image bytes.
  uint32 num_latest_frames = 3;
}

// --- DTMF ---

message SendDTMFCmd {
  string digits = 1;
}

message PublishMessageCmd {
  string topic = 1;
  string message = 2;
  string options_json = 3;
  string payload_json = 4;
}

message SubscribePubSubCmd {
  string topic = 1;
}
message PubSubMessageEvent {
  string topic = 1;
  string message = 2;
  string sender_id = 3;
  string sender_name = 4;
  string timestamp = 5;
  string payload_json = 6;
}

message DTMFEvent {
  string digit = 1;
  string participant_id = 2;
}

// --- Stream enable/disable (legacy `on_stream_enabled` / `on_stream_disabled`) ---

message StreamEventProto {
  string participant_id = 1;
  string kind = 2;
  bool enabled = 3;
}

// --- Raw audio listener (legacy `pipeline.on_audio_delta` parity) ---

message AudioFrameEvent {
  bytes pcm = 1;                     // s16le mono
  uint32 sample_rate = 2;
  string source = 3;                 // "user" | "agent"
  string participant_id = 4;         // empty for agent / pre-mix path
  uint64 timestamp_ms = 5;
}

// --- Vision (per-participant video frame) ---

message VisionFrameEvent {
  bytes data = 1;
  string mime_type = 2;
  uint32 width = 3;
  uint32 height = 4;
  string participant_id = 5;
  uint64 timestamp_ms = 6;
}

// --- Call Transfer ---

message CallTransferCmd {
  string transfer_to = 1;
  string token = 2;
}

// --- SIP / room control-plane RPCs (warm transfer) ---

message SipGetCallInfoRequest { 
  string session_id = 1; 
}

message SipGetCallInfoResponse {
  string call_id = 1;   // empty when found == false
  bool found = 2;       // true if the caller is an active SIP participant
  string error = 3;     // non-empty on failure
}

message SipCreateRoomRequest { 
  string session_id = 1; 
}

message SipCreateRoomResponse {
  string room_id = 1;
  string error = 2;
}

message SipMakeOutboundCallRequest {
  string session_id = 1;
  string routing_rule_id = 2;
  string sip_call_to = 3;
  string sip_call_from = 4;
  string destination_room_id = 5;   // optional; empty = omit
  string participant_name = 6;      // optional; empty = omit
  string extra_options_json = 7;    // optional JSON object forwarded verbatim
}
message SipMakeOutboundCallResponse {
  string call_id = 1;
  string response_json = 2;         // raw backend response
  string error = 3;
}

message SipSwitchCallRoomRequest {
  string session_id = 1;
  string room_id = 2;               // target (consultation) room to move the caller into
  string token = 3;
  string participant_id = 4;
}
message SipSwitchCallRoomResponse {
  string response_json = 1;
  string error = 2;
}

message SipListRoomCallsRequest {
  string session_id = 1;
  string room_id = 2;
}
message SipListRoomCallsResponse {
  string calls_json = 1;   // raw "data" array from GET /v2/sip/call
  string error = 2;
}

message SipEndCallRequest {
  string session_id = 1;
}
message SipEndCallResponse {
  bool success = 1;
  string error = 2;
}

message LlmCompleteRequest {
  string session_id = 1;
  string system_prompt = 2;
  string user_prompt = 3;
}
message LlmCompleteResponse {
  string text = 1;
  string error = 2;
}

message SetUserInputEnabledCmd {
  bool enabled = 1;
}

message UpdateProviderCmd {
  string component = 1;
  string provider = 2;
  map<string, string> params = 3;
}

message ModifyLLMTokenCmd {
  uint64 token_id = 1;
  string replacement = 2;
  bool drop = 3;
}

// --- Voicemail Detection ---

message VoicemailConfig {
  bool enabled = 1;
  float detection_threshold = 2;          // 0.0..1.0; reserved for confidence-based gating (currently binary)
  uint32 max_detection_seconds = 3;       // Window to buffer speech before classifying (default 2s)
  string custom_prompt = 4;               // Override the default classifier system prompt
  bool auto_hangup = 5;                   // If true, runtime triggers session shutdown on detection
}

message VoicemailDetectedEvent {
  float confidence = 1;
  string detected_at_seconds = 2;
  string transcript = 3;
}

// --- Denoise ---

message DenoiseConfig {
  bool enabled = 1;
  string provider = 2;
}

message AvatarProviderConfig {
  string provider   = 1;   // "simli" | "anam"
  string avatar_id  = 2;   // Anam avatar id
  string face_id    = 3;   // Simli face id
  string model      = 4;   // Simli model ("fasttalk" | "artalk"), optional
  bool   is_trinity = 5;   // Simli trinity flag
  map<string, string> params = 10;
}

// --- MCP Integration ---

message MCPServerConfig {
  string type = 1;
  string command = 2;
  repeated string args = 3;
  string url = 4;
  map<string, string> env = 5;
}

// --- Knowledge Base / RAG ---

message KnowledgeBaseConfig {
  bool enabled = 1;
  string provider = 2;
  string index_name = 3;
  uint32 top_k = 4;
  float min_score = 5;
  map<string, string> params = 10;
}

// --- A2A Protocol ---

message A2AMessageEvent {
  string source_agent_id = 1;
  string message_json = 2;
  string correlation_id = 3;
}

message AgentConfigOverride {
  string instructions = 1;
  string greeting = 2;
  string voice_suffix = 3;
  bool append_voice_suffix = 4;
  bool has_append_voice_suffix = 5;          // explicit-set flag (proto3 bool defaults to false)
}

// --- Dispatch (api-server → runtime) ---

message DispatchRequest {
  string agent_id = 1;                       // required: logical agent handle to target
  RoomConfig room = 2;                       // required: where the session runs
  map<string, string> dispatch_metadata = 3;
  AgentConfigOverride agent_override = 4;
  RecordingConfig recording_override = 5;
  SessionLimits limits = 6;
  map<string, string> label_selector = 7;    // optional: constrain to agents matching these labels
  string session_id = 8;
}

message DispatchResponse {
  oneof result {
    DispatchAccepted accepted = 1;
    DispatchRejected rejected = 2;
  }
}

message DispatchAccepted {
  string session_id = 1;
  string worker_id = 2;                      // runtime-assigned worker instance that took the session
}

message DispatchRejected {
  string reason = 1;                         // "no_agent_available", "all_at_capacity", etc.
  string message = 2;
  uint32 registered_agents = 3;
  uint32 available_agents = 4;
}

// --- Terminate (api-server → runtime) ---

message TerminateRequest {
  string session_id = 1;                     // required: id returned by Dispatch
  string reason = 2;                         // optional: "user_hangup", "admin_kick", etc.
}

message TerminateResponse {
  oneof result {
    TerminateAccepted accepted = 1;
    TerminateRejected rejected = 2;
  }
}

message TerminateAccepted {
  string session_id = 1;
  uint64 terminated_at_unix_ms = 2;
}

message TerminateRejected {
  string reason = 1;                         // "session_not_found", "internal_error"
  string message = 2;
}
