// ChatCLI gRPC Service Definition
// Copyright (c) 2024 Edilson Freitas
// License: MIT

syntax = "proto3";

package chatcli.v1;

option go_package = "github.com/diillson/chatcli/proto/chatcli/v1;chatcliv1";

// ChatCLIService exposes ChatCLI functionality over gRPC.
service ChatCLIService {
  // SendPrompt sends a single prompt and returns the LLM response (one-shot mode).
  rpc SendPrompt(SendPromptRequest) returns (SendPromptResponse);

  // StreamPrompt sends a prompt and streams back the response chunks.
  rpc StreamPrompt(StreamPromptRequest) returns (stream StreamPromptResponse);

  // InteractiveSession opens a bidirectional stream for interactive/agent mode.
  rpc InteractiveSession(stream SessionMessage) returns (stream SessionMessage);

  // ListSessions returns all saved session names.
  rpc ListSessions(ListSessionsRequest) returns (ListSessionsResponse);

  // LoadSession loads a previously saved session's message history.
  rpc LoadSession(LoadSessionRequest) returns (LoadSessionResponse);

  // SaveSession saves the current conversation history.
  rpc SaveSession(SaveSessionRequest) returns (SaveSessionResponse);

  // DeleteSession removes a saved session.
  rpc DeleteSession(DeleteSessionRequest) returns (DeleteSessionResponse);

  // GetServerInfo returns server metadata (provider, model, version).
  rpc GetServerInfo(GetServerInfoRequest) returns (GetServerInfoResponse);

  // GetWatcherStatus returns the K8s watcher status (if active).
  rpc GetWatcherStatus(GetWatcherStatusRequest) returns (GetWatcherStatusResponse);

  // Health check for load balancers and k8s probes.
  rpc Health(HealthRequest) returns (HealthResponse);

  // GetAlerts returns current watcher alerts (anomalies detected by the K8s watcher).
  rpc GetAlerts(GetAlertsRequest) returns (GetAlertsResponse);

  // AnalyzeIssue uses the LLM to analyze an AIOps issue and return recommendations.
  rpc AnalyzeIssue(AnalyzeIssueRequest) returns (AnalyzeIssueResponse);

  // AgenticStep runs one turn of the AI-driven remediation agent loop.
  rpc AgenticStep(AgenticStepRequest) returns (AgenticStepResponse);

  // --- Remote Resource Discovery ---

  // ListRemotePlugins returns all plugins installed on the server.
  rpc ListRemotePlugins(ListRemotePluginsRequest) returns (ListRemotePluginsResponse);

  // ListRemoteAgents returns all agents available on the server.
  rpc ListRemoteAgents(ListRemoteAgentsRequest) returns (ListRemoteAgentsResponse);

  // ListRemoteSkills returns all skills available on the server.
  rpc ListRemoteSkills(ListRemoteSkillsRequest) returns (ListRemoteSkillsResponse);

  // GetAgentDefinition returns the full definition of a server-side agent.
  rpc GetAgentDefinition(GetAgentDefinitionRequest) returns (GetAgentDefinitionResponse);

  // GetSkillContent returns the full content of a server-side skill.
  rpc GetSkillContent(GetSkillContentRequest) returns (GetSkillContentResponse);

  // ExecuteRemotePlugin executes a plugin on the server and returns the output.
  rpc ExecuteRemotePlugin(ExecuteRemotePluginRequest) returns (ExecuteRemotePluginResponse);

  // DownloadPlugin streams the plugin binary to the client for offline use.
  rpc DownloadPlugin(DownloadPluginRequest) returns (stream DownloadPluginResponse);

  // --- Conversation Hub (cross-channel continuity) ---

  // ResolveActiveConversation returns the principal's current conversation,
  // creating one on first contact. All channels of the same principal resolve
  // to the same conversation, giving continuity across Telegram/Slack/CLI.
  rpc ResolveActiveConversation(ResolveActiveConversationRequest) returns (ResolveActiveConversationResponse);

  // NewConversation rotates the principal's active-conversation pointer to a
  // fresh thread (what /newsession triggers); every channel follows.
  rpc NewConversation(NewConversationRequest) returns (NewConversationResponse);

  // AppendEvent appends one dialogue turn to a conversation's shared log.
  rpc AppendEvent(AppendEventRequest) returns (AppendEventResponse);

  // ReadConversation returns events with seq > since_seq (for hydration/resume).
  rpc ReadConversation(ReadConversationRequest) returns (ReadConversationResponse);

  // SubscribeConversation live-tails a conversation: backlog after since_seq,
  // then events as they are appended by any channel.
  rpc SubscribeConversation(SubscribeConversationRequest) returns (stream ConversationEvent);

  // SetBinding maps a channel identity (platform,user_id) to a principal.
  rpc SetBinding(SetBindingRequest) returns (SetBindingResponse);

  // ListBindings returns channel→principal bindings (optionally filtered).
  rpc ListBindings(ListBindingsRequest) returns (ListBindingsResponse);
}

// --- SendPrompt ---

message SendPromptRequest {
  string prompt = 1;
  repeated ChatMessage history = 2;
  int32 max_tokens = 3;
  string provider = 4;  // optional: override server's default provider
  string model = 5;     // optional: override server's default model
  string client_api_key = 6; // optional: client's own API key/OAuth token (overrides server credentials)
  map<string, string> provider_config = 7; // optional: provider-specific config (StackSpot realm/agent_id/client_id/client_key, Ollama base_url, etc.)
}

message SendPromptResponse {
  string response = 1;
  string model = 2;
  string provider = 3;
}

// --- StreamPrompt ---

message StreamPromptRequest {
  string prompt = 1;
  repeated ChatMessage history = 2;
  int32 max_tokens = 3;
  string provider = 4;
  string model = 5;
  string client_api_key = 6; // optional: client's own API key/OAuth token
  map<string, string> provider_config = 7; // optional: provider-specific config
}

message StreamPromptResponse {
  string chunk = 1;       // partial response text
  bool done = 2;          // true when stream is complete
  string model = 3;
  string provider = 4;
}

// --- InteractiveSession ---

message SessionMessage {
  enum Type {
    USER_INPUT = 0;
    ASSISTANT_RESPONSE = 1;
    SYSTEM_MESSAGE = 2;
    ERROR = 3;
    COMMAND = 4;          // CLI commands like /agent, /coder, /switch
    COMMAND_RESULT = 5;
  }
  Type type = 1;
  string content = 2;
  map<string, string> metadata = 3;  // extra info (e.g., provider, model, mode)
}

// --- ChatMessage (shared) ---

message MessageMeta {
  bool is_summary = 1;
  int32 summary_of = 2;
  string mode = 3;       // "chat", "agent", "coder"
}

message ChatMessage {
  string role = 1;     // "user" or "assistant"
  string content = 2;
  MessageMeta meta = 3;  // optional metadata for history compaction
}

// --- Session Data V2 (scoped histories) ---

message SessionDataV2 {
  int32 version = 1;
  repeated ChatMessage chat_history = 2;
  repeated ChatMessage agent_history = 3;
  repeated ChatMessage coder_history = 4;
  repeated ChatMessage shared_memory = 5;
}

// --- Session Management ---

message ListSessionsRequest {}

message ListSessionsResponse {
  repeated string sessions = 1;
}

message LoadSessionRequest {
  string name = 1;
}

message LoadSessionResponse {
  repeated ChatMessage messages = 1;       // v1 compat (always populated with chat_history)
  SessionDataV2 session_data = 2;          // v2: full scoped histories (takes priority when present)
}

message SaveSessionRequest {
  string name = 1;
  repeated ChatMessage messages = 2;       // v1 compat (always populated with chat_history)
  SessionDataV2 session_data = 3;          // v2: full scoped histories (takes priority when present)
}

message SaveSessionResponse {
  bool success = 1;
}

message DeleteSessionRequest {
  string name = 1;
}

message DeleteSessionResponse {
  bool success = 1;
}

// --- Server Info ---

message GetServerInfoRequest {}

message GetServerInfoResponse {
  string version = 1;
  string provider = 2;
  string model = 3;
  repeated string available_providers = 4;
  bool watcher_active = 5;     // true if K8s watcher is running
  string watcher_target = 6;   // e.g., "namespace/deployment"
  int32 plugin_count = 7;      // number of plugins available on server
  int32 agent_count = 8;       // number of agents available on server
  int32 skill_count = 9;       // number of skills available on server
}

// --- Watcher Status ---

message GetWatcherStatusRequest {}

message GetWatcherStatusResponse {
  bool active = 1;
  string deployment = 2;
  string namespace = 3;
  string status_summary = 4;    // compact one-line status
  int32 alert_count = 5;
  int32 snapshot_count = 6;
  int32 pod_count = 7;
}

// --- Health ---

message HealthRequest {}

message HealthResponse {
  enum Status {
    SERVING = 0;
    NOT_SERVING = 1;
  }
  Status status = 1;
  string version = 2;
}

// --- Watcher Alerts (AIOps) ---

message WatcherAlert {
  string type = 1;            // e.g., "HighRestartCount", "OOMKilled", "PodNotReady", "DeploymentFailing"
  string severity = 2;        // "info", "warning", "critical"
  string message = 3;
  string object = 4;          // e.g., pod name
  string namespace = 5;
  string deployment = 6;
  int64 timestamp_unix = 7;
}

message GetAlertsRequest {
  string namespace = 1;       // optional: filter by namespace
  string deployment = 2;      // optional: filter by deployment
}

message GetAlertsResponse {
  repeated WatcherAlert alerts = 1;
}

// --- AI Issue Analysis (AIOps) ---

message AnalyzeIssueRequest {
  string issue_name = 1;
  string namespace = 2;
  string resource_kind = 3;
  string resource_name = 4;
  string signal_type = 5;     // e.g., "error_rate", "oom_kill", "pod_restart"
  string severity = 6;        // "low", "medium", "high", "critical"
  string description = 7;
  int32 risk_score = 8;
  string provider = 9;        // optional: override LLM provider
  string model = 10;          // optional: override LLM model
  string kubernetes_context = 11;         // deployment status, pods, events, revision history
  string previous_failure_context = 12;   // results from previous remediation attempts (for retries)
}

message AnalyzeIssueResponse {
  string analysis = 1;
  float confidence = 2;       // 0.0 to 1.0
  repeated string recommendations = 3;
  string model = 4;
  string provider = 5;
  repeated SuggestedAction suggested_actions = 6;
}

message SuggestedAction {
  string name = 1;                    // human-readable step name, e.g., "Restart pods"
  string action = 2;                  // action type: ScaleDeployment, RollbackDeployment, RestartDeployment, PatchConfig, AdjustResources, DeletePod, HelmRollback, ArgoSyncApp, AdjustHPA, RestartStatefulSetPod, CordonNode, DrainNode, ResizePVC, RotateSecret, ExecDiagnostic, UpdateIngress, PatchNetworkPolicy, ApplyManifest, Custom
  string description = 3;            // explanation of why this action is recommended
  map<string, string> params = 4;    // action-specific params, e.g., {"replicas": "3"}
}

// --- Agentic Remediation ---

message AgenticHistoryEntry {
  int32 step_number = 1;
  string ai_message = 2;
  string action = 3;                  // action type executed (e.g., "RestartDeployment")
  map<string, string> params = 4;     // action params
  string observation = 5;             // result of executing the action
}

message AgenticStepRequest {
  string issue_name = 1;
  string namespace = 2;
  string resource_kind = 3;
  string resource_name = 4;
  string signal_type = 5;
  string severity = 6;
  string description = 7;
  int32 risk_score = 8;
  string provider = 9;
  string model = 10;
  string kubernetes_context = 11;              // fresh K8s snapshot (refreshed each step)
  repeated AgenticHistoryEntry history = 12;   // full conversation history
  int32 max_steps = 13;                        // step budget
  int32 current_step = 14;                     // step number being computed now
  // Primary guidance from the prior AIInsight analysis. Injected as authoritative
  // context so the agentic loop cannot silently contradict its own root-cause analysis.
  // GAP-01 fix (chaos test report 2026-05-23).
  string insight_analysis = 15;                // AIInsight.Status.Analysis text
  float insight_confidence = 16;               // 0.0–1.0 confidence from AIInsight
  repeated string insight_recommendations = 17;
  repeated SuggestedAction insight_suggested_actions = 18;
}

message AgenticStepResponse {
  string reasoning = 1;                        // AI's current reasoning (recorded in history)
  bool resolved = 2;                           // true = AI considers problem fixed
  SuggestedAction next_action = 3;             // null/empty when resolved=true
  // Fields below only populated when resolved=true:
  string postmortem_summary = 4;
  string root_cause = 5;
  string impact = 6;
  repeated string lessons_learned = 7;
  repeated string prevention_actions = 8;
  // Divergence signal: set true when next_action contradicts the AIInsight's
  // primary suggested action without an explicit justification. The operator
  // logs this and (when divergence_reason is empty) may decline to execute.
  // GAP-01 fix.
  bool diverges_from_insight = 9;
  string divergence_reason = 10;               // LLM-provided justification when intentionally diverging
}

// =============================================================================
// Remote Resource Discovery
// =============================================================================

// --- Plugin Discovery ---

message PluginInfo {
  string name = 1;
  string description = 2;
  string usage = 3;
  string version = 4;
  string schema = 5;            // JSON schema describing plugin arguments/subcommands
}

message ListRemotePluginsRequest {}

message ListRemotePluginsResponse {
  repeated PluginInfo plugins = 1;
}

// --- Agent Discovery ---

message AgentInfo {
  string name = 1;
  string description = 2;
  repeated string skills = 3;   // skill names required by this agent
  repeated string plugins = 4;  // plugin names required by this agent
  string model = 5;             // preferred model for this agent
  string content = 6;           // markdown body (system prompt content)
  string frontmatter = 7;       // raw YAML frontmatter for client-side parsing

  // Per-agent LLM profile (Fase: multi-agent parity with skills).
  // effort drives extended thinking / reasoning_effort on providers
  // that support it. model already exists above as field 5. Both are
  // honored by the dispatcher's model router when this agent runs as a
  // worker — empty string means "inherit user's active model / send no
  // effort hint".
  string effort = 8;            // "low" | "medium" | "high" | "max" | ""

  // Optional metadata mirroring skill frontmatter — useful for
  // `/agent list`, catalog display, and future registry search.
  string category = 9;
  string version = 10;
  string author = 11;
  repeated string tags = 12;
}

message ListRemoteAgentsRequest {}

message ListRemoteAgentsResponse {
  repeated AgentInfo agents = 1;
}

// --- Skill Discovery ---

message SkillInfo {
  string name = 1;
  string description = 2;
  string content = 3;                       // markdown body
  repeated string allowed_tools = 4;
  map<string, string> subskills = 5;        // name -> markdown content
  map<string, string> scripts = 6;          // name -> server-side absolute path
}

message ListRemoteSkillsRequest {}

message ListRemoteSkillsResponse {
  repeated SkillInfo skills = 1;
}

// --- Get Specific Agent/Skill ---

message GetAgentDefinitionRequest {
  string name = 1;
}

message GetAgentDefinitionResponse {
  AgentInfo agent = 1;
}

message GetSkillContentRequest {
  string name = 1;
}

message GetSkillContentResponse {
  SkillInfo skill = 1;
}

// --- Remote Plugin Execution ---

message ExecuteRemotePluginRequest {
  string plugin_name = 1;
  repeated string args = 2;
  bool stream = 3;              // if true, server streams output progressively
}

message ExecuteRemotePluginResponse {
  string output = 1;
  string error = 2;
  bool done = 3;                // true when execution is complete
}

// --- Plugin Download (Hybrid Mode) ---

message DownloadPluginRequest {
  string plugin_name = 1;
  string target_os = 2;         // client's runtime.GOOS
  string target_arch = 3;       // client's runtime.GOARCH
}

message DownloadPluginResponse {
  bytes chunk = 1;              // binary chunk
  string filename = 2;          // plugin filename (set in first chunk)
  int64 total_size = 3;         // total file size in bytes (set in first chunk)
  bool done = 4;                // true when download is complete
}

// --- Conversation Hub (cross-channel continuity) ---

// ConversationEvent mirrors models.ConversationEvent: one turn in the shared,
// append-only cross-channel conversation log.
message ConversationEvent {
  string conv_id = 1;
  int64 seq = 2;                // server-assigned, monotonic per conversation
  string principal = 3;
  string channel = 4;           // "telegram", "slack", "local", ...
  string role = 5;              // user | assistant | tool_summary | checkpoint
  string content = 6;
  string client_msg_id = 7;     // idempotency key
  int64 ts = 8;                 // unix millis
}

message ResolveActiveConversationRequest {
  string principal = 1;         // optional; server falls back to the authenticated principal
}
message ResolveActiveConversationResponse {
  string conv_id = 1;
  string principal = 2;
}

message NewConversationRequest {
  string principal = 1;         // optional; defaults to authenticated principal
}
message NewConversationResponse {
  string conv_id = 1;
}

message AppendEventRequest {
  string conv_id = 1;
  string channel = 2;
  string role = 3;
  string content = 4;
  string client_msg_id = 5;     // optional idempotency key
}
message AppendEventResponse {
  ConversationEvent event = 1;
}

message ReadConversationRequest {
  string conv_id = 1;
  int64 since_seq = 2;
  int32 limit = 3;              // 0 = no limit
}
message ReadConversationResponse {
  repeated ConversationEvent events = 1;
}

message SubscribeConversationRequest {
  string conv_id = 1;
  int64 since_seq = 2;
}

message SetBindingRequest {
  string platform = 1;
  string user_id = 2;
  string principal = 3;
}
message SetBindingResponse {
  bool success = 1;
}

message ListBindingsRequest {
  string principal = 1;         // optional filter (empty = all)
}
message HubBinding {
  string platform = 1;
  string user_id = 2;
  string principal = 3;
}
message ListBindingsResponse {
  repeated HubBinding bindings = 1;
}
