vik 0.1.2

Vik is an issue-driven coding workflow automation tool.
use serde::Deserialize;
use serde_json::Value;

#[derive(Debug, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub(super) enum ClaudeEvent {
  System(SystemEvent),
  Assistant(MessageEvent),
  User(MessageEvent),
  Result(ResultEvent),
  #[serde(other)]
  Unknown,
}

#[derive(Debug, Deserialize)]
#[serde(tag = "subtype", rename_all = "snake_case")]
pub(super) enum SystemEvent {
  Init {
    session_id: String,
  },
  #[serde(other)]
  Unknown,
}

#[derive(Debug, Deserialize)]
pub(super) struct MessageEvent {
  pub message: ClaudeMessage,
}

#[derive(Debug, Deserialize)]
pub(super) struct ClaudeMessage {
  #[serde(default)]
  pub content: MessageContent,
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub(super) enum MessageContent {
  Blocks(Vec<ContentBlock>),
  #[allow(dead_code)]
  Text(String),
}

impl Default for MessageContent {
  fn default() -> Self {
    Self::Text(String::new())
  }
}

impl MessageContent {
  pub(super) fn blocks(&self) -> &[ContentBlock] {
    match self {
      Self::Blocks(blocks) => blocks,
      Self::Text(_) => &[],
    }
  }
}

#[derive(Debug, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub(super) enum ContentBlock {
  Text {
    text: String,
  },
  ToolUse {
    id: String,
    name: ToolName,
    input: Value,
  },
  ToolResult {
    tool_use_id: String,
    content: Value,
    #[allow(dead_code)]
    is_error: Option<bool>,
  },
  #[serde(other)]
  Unknown,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub(super) enum ToolName {
  Known(KnownToolName),
  Other(String),
}

impl ToolName {
  pub(super) fn as_str(&self) -> &str {
    match self {
      Self::Known(name) => name.as_str(),
      Self::Other(name) => name.as_str(),
    }
  }

  pub(super) fn is_subagent(&self) -> bool {
    matches!(self, Self::Known(KnownToolName::Agent | KnownToolName::Task))
  }
}

#[derive(Debug, Clone, Deserialize)]
pub(super) enum KnownToolName {
  Agent,
  Task,
}

impl KnownToolName {
  fn as_str(&self) -> &'static str {
    match self {
      Self::Agent => "Agent",
      Self::Task => "Task",
    }
  }
}

#[derive(Debug, Deserialize)]
pub(super) struct ResultEvent {
  pub usage: Option<TokenUsage>,
}

#[derive(Debug, Deserialize)]
pub(super) struct TokenUsage {
  #[serde(default)]
  pub input_tokens: u64,
  #[serde(default)]
  pub output_tokens: u64,
  #[serde(default)]
  pub cache_read_input_tokens: u64,
}

#[derive(Debug, Deserialize)]
struct EventType {
  #[serde(rename = "type")]
  kind: String,
}

pub(super) fn parse(value: &Value) -> Result<ClaudeEvent, serde_json::Error> {
  serde_json::from_value(value.clone())
}

pub(super) fn event_type(value: &Value) -> Option<String> {
  serde_json::from_value::<EventType>(value.clone()).ok().map(|event| event.kind)
}