use crate::elicitation::ElicitationRequest;
use crate::state::*;
use crate::usage::ProviderCost;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::sync::Arc;
#[derive(Debug)]
pub struct ProjectionIntegrityError(pub String);
impl std::fmt::Display for ProjectionIntegrityError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.0)
}
}
impl std::error::Error for ProjectionIntegrityError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StoreSchemaMismatch {
pub found: i64,
pub supported: i64,
pub reason: StoreSchemaMismatchReason,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StoreSchemaMismatchReason {
NeedsMigration,
Incompatible { minimum_compatible: i64 },
InvalidCompatibilityMetadata,
Rollback { previous: i64 },
}
impl std::fmt::Display for StoreSchemaMismatch {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
found,
supported,
reason,
} = self;
match reason {
StoreSchemaMismatchReason::Incompatible { minimum_compatible } => write!(
formatter,
"Mjolnir database schema {found} requires at least build schema {minimum_compatible} for reads and writes; this build supports {supported}; upgrade Mjolnir, run this build with an isolated data directory (--instance NAME or MJ_DATA_DIR), or restore a backup made by the older build"
),
StoreSchemaMismatchReason::NeedsMigration => write!(
formatter,
"Mjolnir database schema {found} is not the supported schema {supported}; start the Mjolnir daemon to migrate it"
),
StoreSchemaMismatchReason::InvalidCompatibilityMetadata => write!(
formatter,
"Mjolnir database schema {found} has missing or invalid compatibility metadata; refusing access from build schema {supported}"
),
StoreSchemaMismatchReason::Rollback { previous } => write!(
formatter,
"Mjolnir database schema rolled back from {previous} to {found} underneath this writer; refusing writes from build schema {supported}"
),
}
}
}
impl std::error::Error for StoreSchemaMismatch {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HistoryScope {
Project,
Session,
All,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PromptHistoryEntry {
pub id: i64,
pub session_id: String,
pub text: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProjectionApplyOutcome {
Applied,
AlreadyApplied,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TranscriptMutation {
Upsert(TranscriptItem),
Remove { stable_id: String },
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct MaterializedSessionMutation {
pub last_activity_at_ms: Option<i64>,
pub execution: Option<MaterializedExecutionState>,
pub session_title: Option<Option<String>>,
pub configuration: Option<BTreeMap<String, serde_json::Value>>,
pub transcript: Vec<TranscriptMutation>,
pub queued_prompts: Option<Vec<MaterializedQueuedPrompt>>,
pub pending_elicitations: Option<Vec<crate::elicitation::ElicitationRequest>>,
pub active_turn: Option<Option<MaterializedTurn>>,
pub last_turn_outcome: Option<MaterializedTurnOutcome>,
pub config_results: Vec<(String, Option<String>)>,
pub provider_cost: Option<crate::usage::ProviderCost>,
pub api_events: Vec<ApiEventData>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ClientSessionState {
pub draft: String,
pub through_event_ordinal: u64,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct DetachedSessionDraft {
pub text: String,
pub inherited_input: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TurnSummary {
pub turn_number: u64,
pub turn_started_at_ms: i64,
pub last_changed_at_ms: i64,
pub final_message: Option<String>,
}
#[derive(Debug, Clone)]
pub struct TranscriptPage {
pub items: Vec<Arc<TranscriptItem>>,
pub latest_seq: u64,
pub next_after_seq: u64,
pub execution: MaterializedExecutionState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TranscriptRetention {
pub items: usize,
pub bytes: usize,
pub remaining: bool,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct StoredReview {
pub workflow: crate::second_opinion::ReviewWorkflow,
pub generation: u64,
pub context_baseline: u64,
pub native_lost: bool,
pub reviewer_transcript: Vec<std::sync::Arc<crate::state::TranscriptItem>>,
}
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct TurnReviewState {
pub baselines: std::collections::BTreeMap<std::path::PathBuf, String>,
pub reviewed_through_ordinal: u64,
pub prior_review: Option<crate::review::lanes::PriorReviewContext>,
pub active: Option<String>,
#[serde(default)]
pub pending_forward: Option<crate::review::driver::PendingForward>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BoundedPromptHistory {
pub entries: Vec<PromptHistoryEntry>,
pub truncated: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct UsageCoverage {
pub recorded_turns: u64,
pub full_turn_reports: u64,
pub last_request_reports: u64,
pub unspecified_reports: u64,
pub missing_reports: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UsageCounterTotal {
pub tokens: u64,
pub reported_turns: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UsagePage {
pub session_id: String,
pub turns: Vec<MaterializedTurnOutcome>,
pub next_after_seq: u64,
pub latest_seq: u64,
pub totals: BTreeMap<String, UsageCounterTotal>,
pub coverage: UsageCoverage,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_session_cost: Option<ProviderCost>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ApiEvent {
pub seq: u64,
pub session_id: String,
pub recorded_at_ms: i64,
#[serde(flatten)]
pub event: ApiEventData,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum ApiEventData {
TurnStarted {
turn: MaterializedTurn,
},
TurnEnded {
turn: MaterializedTurnOutcome,
},
Error {
message: String,
command_id: Option<String>,
},
InputRequired {
request: ElicitationRequest,
turn_id: Option<u64>,
},
InputResolved {
elicitation_id: String,
turn_id: Option<u64>,
action: String,
},
ActivityChanged {
activity: ApiActivityState,
},
}
impl ApiEventData {
pub fn kind(&self) -> &'static str {
match self {
Self::TurnStarted { .. } => "turn_started",
Self::TurnEnded { .. } => "turn_ended",
Self::Error { .. } => "error",
Self::InputRequired { .. } => "input_required",
Self::InputResolved { .. } => "input_resolved",
Self::ActivityChanged { .. } => "activity_changed",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApiActivityState {
pub state: String,
pub details: Option<ApiActivityDetails>,
pub is_idle: bool,
pub waiting_for_input: bool,
pub capacity_retry: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ApiActivityDetails {
pub kind: ApiActivityKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub turn_started_at_ms: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub step_started_at_ms: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub background_started_at_ms: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub idle_since_ms: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ApiActivityKind {
Turn,
Step,
Background,
Idle,
Lifecycle,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApiEventFilter {
pub session_id: Option<String>,
pub workspace_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ApiEventPage {
pub events: Vec<ApiEvent>,
pub next_after_seq: u64,
pub latest_seq: u64,
}