goosedump 0.12.43

Browse, search, compact, and learn from coding-agent sessions
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) Jarkko Sakkinen 2026

use std::str::FromStr;

use serde::{Deserialize, Serialize};

/// How a provider persists sessions on disk.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum StorageKind {
    /// One transcript file (or directory of files) per session.
    File,
    /// Sessions live as rows in a provider `SQLite` database.
    Sqlite,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum Client {
    Claude,
    Codex,
    Crush,
    Gemini,
    Goose,
    Opencode,
    Pi,
}

impl Client {
    pub(crate) const ALL: [Self; 7] = [
        Self::Claude,
        Self::Codex,
        Self::Crush,
        Self::Gemini,
        Self::Goose,
        Self::Opencode,
        Self::Pi,
    ];

    #[must_use]
    pub(crate) const fn as_str(self) -> &'static str {
        match self {
            Self::Claude => "claude",
            Self::Codex => "codex",
            Self::Crush => "crush",
            Self::Gemini => "gemini",
            Self::Goose => "goose",
            Self::Opencode => "opencode",
            Self::Pi => "pi",
        }
    }

    #[must_use]
    pub(crate) const fn storage(self) -> StorageKind {
        match self {
            Self::Claude | Self::Codex | Self::Gemini | Self::Pi => StorageKind::File,
            Self::Crush | Self::Goose | Self::Opencode => StorageKind::Sqlite,
        }
    }
}

impl std::fmt::Display for Client {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_str())
    }
}

impl AsRef<str> for Client {
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ParseClientError;

impl std::fmt::Display for ParseClientError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "provider must be one of: {}",
            Client::ALL.map(Client::as_str).join(", ")
        )
    }
}

impl std::error::Error for ParseClientError {}

impl FromStr for Client {
    type Err = ParseClientError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        Self::ALL
            .into_iter()
            .find(|client| client.as_str() == value)
            .ok_or(ParseClientError)
    }
}