//! These types are named after the database tables, and are used to represent specific queries. use std::collections::{BTreeMap, HashMap, HashSet}; use chrono::NaiveDateTime; use diesel::prelude::*; use serde::{Deserialize, Deserializer, Serialize}; use warp_multi_agent_api::response_event::stream_finished; use warp_multi_agent_api::{self as api}; use super::schema::{ active_mcp_servers, agent_conversations, agent_tasks, ai_document_panes, ai_memory_panes, ambient_agent_panes, app, blocks, cloud_objects_refreshes, code_pane_tabs, code_panes, code_review_panes, commands, current_user_information, env_var_collection_panes, folders, generic_string_objects, ignored_suggestions, mcp_environment_variables, mcp_server_installations, mcp_server_panes, notebook_panes, notebooks, object_actions, object_metadata, object_permissions, pane_branches, pane_leaves, pane_nodes, panels, project_rules, projects, server_experiments, settings_panes, tab_groups, tabs, team_members, team_settings, teams, terminal_panes, user_profiles, windows, workflow_panes, workflows, workspace_language_server, workspace_metadata, workspace_teams, workspaces, }; #[derive(Insertable)] #[diesel(table_name = app)] pub struct NewApp { pub active_window_id: Option, } #[derive(Identifiable, Queryable)] pub struct Window { pub id: i32, pub active_tab_index: i32, pub window_width: Option, pub window_height: Option, pub origin_x: Option, pub origin_y: Option, pub quake_mode: bool, pub universal_search_width: Option, pub warp_ai_width: Option, pub voltron_width: Option, pub warp_drive_index_width: Option, pub fullscreen_state: i32, pub agent_management_filters: Option, pub left_panel_open: Option, pub vertical_tabs_panel_open: Option, } #[derive(Identifiable, Insertable, Queryable)] pub struct GenericStringObject { pub id: i32, pub data: String, } #[derive(Insertable)] #[diesel(table_name = generic_string_objects)] pub struct NewGenericStringObject<'a> { pub data: &'a str, } #[derive(Insertable, Queryable)] pub struct Workflow { pub id: i32, pub data: String, } /// A type representing a `Workflow` that is newly created. We purposefully /// do not include the `id` here since it is unset. #[derive(Insertable)] #[diesel(table_name = workflows)] pub struct NewWorkflow { pub data: String, } #[derive(Identifiable, Insertable, Queryable)] pub struct Notebook { pub id: i32, pub title: Option, pub data: Option, pub ai_document_id: Option, } #[derive(Insertable)] #[diesel(table_name = notebooks)] pub struct NewNotebook { pub title: Option, pub data: Option, pub ai_document_id: Option, } #[derive(Insertable, Identifiable, Queryable)] pub struct Folder { pub id: i32, pub name: String, pub is_open: bool, pub is_warp_pack: bool, } #[derive(Insertable)] #[diesel(table_name = folders)] pub struct NewFolder { pub name: String, pub is_open: bool, pub is_warp_pack: bool, } #[derive(Identifiable, Insertable, Queryable)] pub struct Team { pub id: i32, pub name: String, pub server_uid: String, pub billing_metadata_json: Option, } #[derive(Insertable, AsChangeset)] #[diesel(table_name = teams)] pub struct NewTeam { pub name: String, pub server_uid: String, pub billing_metadata_json: Option, } #[derive(Identifiable, Queryable)] #[diesel(table_name = team_members)] pub struct TeamMemberRow { pub id: i32, pub team_id: i32, pub user_uid: String, pub email: String, pub role: String, } #[derive(Insertable)] #[diesel(table_name = team_members)] pub struct NewTeamMember { pub team_id: i32, pub user_uid: String, pub email: String, pub role: String, } #[derive(Identifiable, Insertable, Queryable)] pub struct Workspace { pub id: i32, pub name: String, pub server_uid: String, pub is_selected: bool, } #[derive(Insertable, AsChangeset)] #[diesel(table_name = workspaces)] pub struct NewWorkspace { pub name: String, pub server_uid: String, pub is_selected: bool, } #[derive(Identifiable, Insertable, Queryable)] pub struct TeamSetting { pub id: i32, pub team_id: i32, pub settings_json: String, } #[derive(Insertable, AsChangeset)] #[diesel(table_name = team_settings)] pub struct NewTeamSettings { pub team_id: i32, pub settings_json: String, } #[derive(Clone, Identifiable, Insertable, Queryable, AsChangeset)] #[diesel(table_name = project_rules)] pub struct ProjectRules { pub id: i32, pub path: String, pub project_root: String, } #[derive(Clone, Debug, Insertable, AsChangeset)] #[diesel(table_name = project_rules)] pub struct NewProjectRules { pub path: String, pub project_root: String, } #[derive(Clone, Identifiable, Queryable, AsChangeset)] #[diesel(table_name = workspace_metadata)] pub struct WorkspaceMetadata { pub id: i32, pub repo_path: String, pub navigated_ts: Option, pub modified_ts: Option, pub queried_ts: Option, } #[derive(Clone, Insertable, AsChangeset)] #[diesel(table_name = workspace_metadata)] pub struct NewWorkspaceMetadata { pub repo_path: String, pub navigated_ts: Option, pub modified_ts: Option, pub queried_ts: Option, } #[derive(Clone, Identifiable, Insertable, Queryable, AsChangeset)] #[diesel(table_name = workspace_language_server)] pub struct WorkspaceLanguageServer { pub id: i32, pub workspace_id: i32, pub language_server_name: String, pub enabled: String, } #[derive(Clone, Insertable, AsChangeset)] #[diesel(table_name = workspace_language_server)] pub struct NewWorkspaceLanguageServer { pub workspace_id: i32, pub language_server_name: String, pub enabled: String, } #[derive(Default, Clone, Debug, Insertable, Queryable, AsChangeset)] #[diesel(table_name = projects)] pub struct Project { pub path: String, pub added_ts: NaiveDateTime, pub last_opened_ts: Option, } impl Project { pub fn last_used_at(&self) -> NaiveDateTime { self.last_opened_ts.unwrap_or(self.added_ts) } } impl PartialEq for Project { fn eq(&self, other: &Self) -> bool { self.path == other.path } } impl Eq for Project {} #[derive(Identifiable, Insertable, Queryable)] pub struct WorkspaceTeam { pub id: i32, pub workspace_server_uid: String, pub team_server_uid: String, } #[derive(Insertable, AsChangeset)] #[diesel(table_name = workspace_teams)] pub struct NewWorkspaceTeam { pub workspace_server_uid: String, pub team_server_uid: String, } #[derive(Insertable, Queryable)] #[diesel(table_name = object_permissions)] pub struct ObjectPermissions { pub id: i32, pub object_metadata_id: i32, pub subject_type: String, pub subject_id: Option, pub subject_uid: String, pub permissions_last_updated_at: Option, pub object_guests: Option>, pub anyone_with_link_access_level: Option, pub anyone_with_link_source: Option>, } #[derive(Insertable, Queryable)] #[diesel(table_name = object_permissions)] pub struct NewObjectPermissions { pub object_metadata_id: i32, pub subject_type: String, pub subject_id: Option, pub subject_uid: String, pub permissions_last_updated_at: Option, pub object_guests: Option>, pub anyone_with_link_access_level: Option<&'static str>, pub anyone_with_link_source: Option>, } #[derive(Insertable, Queryable)] #[diesel(table_name = object_metadata)] pub struct ObjectMetadata { pub id: i32, pub is_pending: bool, pub object_type: String, pub revision_ts: Option, pub server_id: Option, pub client_id: Option, pub shareable_object_id: i32, pub author_id: Option, pub retry_count: i32, pub metadata_last_updated_ts: Option, pub trashed_ts: Option, pub folder_id: Option, pub is_welcome_object: bool, pub creator_uid: Option, pub last_editor_uid: Option, pub current_editor: Option, } #[derive(Insertable, Queryable)] #[diesel(table_name = object_metadata)] pub struct NewObjectMetadata { pub is_pending: bool, pub object_type: String, pub revision_ts: Option, pub server_id: Option, pub client_id: Option, pub shareable_object_id: i32, pub author_id: Option, pub retry_count: i32, pub metadata_last_updated_ts: Option, pub trashed_ts: Option, pub folder_id: Option, pub is_welcome_object: bool, pub creator_uid: Option, pub last_editor_uid: Option, pub current_editor: Option, } #[derive(Insertable)] #[diesel(table_name = windows)] pub struct NewWindow { pub active_tab_index: i32, pub window_width: Option, pub window_height: Option, pub origin_x: Option, pub origin_y: Option, pub quake_mode: bool, pub universal_search_width: Option, pub warp_ai_width: Option, pub voltron_width: Option, pub warp_drive_index_width: Option, pub fullscreen_state: i32, pub agent_management_filters: Option, pub left_panel_open: Option, pub vertical_tabs_panel_open: Option, } #[derive(Identifiable, Queryable, Associations)] #[diesel(belongs_to(Window))] pub struct Tab { pub id: i32, pub window_id: i32, pub custom_title: Option, pub color: Option, pub tab_group_id: Option, pub pinned: bool, } #[derive(Insertable)] #[diesel(table_name = tabs)] pub struct NewTab { pub window_id: i32, pub custom_title: Option, pub color: Option, pub tab_group_id: Option, pub pinned: bool, } /// Persisted form of a tab group. `name` is optional — untitled groups omit /// it and the UI falls back to a default label. #[derive(Identifiable, Queryable, Associations)] #[diesel(belongs_to(Window))] #[diesel(table_name = tab_groups)] pub struct TabGroup { pub id: i32, pub window_id: i32, pub name: Option, pub color: Option, pub collapsed: bool, pub pinned: bool, } #[derive(Insertable)] #[diesel(table_name = tab_groups)] pub struct NewTabGroup { pub window_id: i32, pub name: Option, pub color: Option, pub collapsed: bool, pub pinned: bool, } /// The panes data model includes pane_nodes, pane_leaves and pane_branches. /// In addition, each kind of pane has a table for its specific data (i.e. the cwd for terminal panes). /// The pane_nodes table specifically keeps the node data so it is responsible for /// keeping track of the tree relationships. /// The pane_leaves table keeps info about a given pane (i.e. what kind of pane it is). /// The pane_branches table keeps info about a branch in the tree (e.g. whether /// the branch splits horizontally or vertically). #[derive(Identifiable, Queryable)] #[diesel(table_name = pane_leaves)] #[diesel(primary_key(pane_node_id, kind))] pub struct PaneLeaf { pub pane_node_id: i32, pub kind: String, pub is_focused: bool, pub custom_vertical_tabs_title: Option, } #[derive(Identifiable, Queryable, Selectable)] #[diesel(table_name = terminal_panes)] #[diesel(primary_key(id))] pub struct TerminalPane { pub id: i32, // This is hardcoded in the database, and not used in the app, but Diesel requires it so that // fields are in the same order as the table's columns. pub kind: String, pub uuid: Vec, pub cwd: Option, pub is_active: bool, /// This is serialized JSON data for a ShellLaunchData struct. pub shell_launch_data: Option, /// This is serialized JSON data for an InputConfig struct. pub input_config: Option, pub llm_model_override: Option, pub active_profile_id: Option, /// This is serialized JSON data for a Vec. pub conversation_ids: Option, /// The active conversation ID if the agent view was open in fullscreen mode. pub active_conversation_id: Option, } #[derive(Identifiable, Queryable, Selectable)] #[diesel(table_name = notebook_panes)] #[diesel(primary_key(id))] pub struct NotebookPane { pub id: i32, // This is hardcoded in the database, and not used in the app, but Diesel requires it so that // fields are in the same order as the table's columns. pub kind: String, pub notebook_id: Option, pub local_path: Option>, } #[derive(Identifiable, Queryable, Selectable)] #[diesel(table_name = env_var_collection_panes)] #[diesel(primary_key(id))] pub struct EnvVarCollectionPane { pub id: i32, pub kind: String, pub env_var_collection_id: Option, } #[derive(Identifiable, Queryable, Selectable)] #[diesel(table_name = workflow_panes)] #[diesel(primary_key(id))] pub struct WorkflowPane { pub id: i32, pub kind: String, pub workflow_id: Option, } #[derive(Identifiable, Queryable, Selectable)] #[diesel(table_name = code_panes)] #[diesel(primary_key(id))] pub struct CodePane { pub id: i32, pub active_tab_index: i32, pub source_data: Option, } #[derive(Identifiable, Queryable, Selectable)] #[diesel(table_name = code_pane_tabs)] #[diesel(primary_key(id))] pub struct CodePaneTab { pub id: i32, pub code_pane_id: i32, pub tab_index: i32, pub local_path: Option>, } #[derive(Identifiable, Queryable, Selectable)] #[diesel(table_name = code_review_panes)] #[diesel(primary_key(id))] pub struct CodeReviewPane { pub id: i32, pub kind: String, pub terminal_uuid: Vec, pub repo_path: String, } #[derive(Identifiable, Queryable, Selectable)] #[diesel(table_name = settings_panes)] #[diesel(primary_key(id))] pub struct SettingsPane { pub id: i32, pub kind: String, pub current_page: String, } /// Maps to the `ai_memory_panes` table /// (where table name is historical and not worth a migration to change). #[derive(Identifiable, Queryable, Selectable)] #[diesel(table_name = ai_memory_panes)] #[diesel(primary_key(id))] pub struct AIFactPane { pub id: i32, pub kind: String, } /// Subset of the [`terminal_panes`] table needed to retrieve per-session block lists. /// /// The true primary key of the table is [`terminal_panes::id`]. However, Diesel's associations API /// requires matching on the primary key, so this view pretends that [`terminal_panes::uuid`] is /// the primary key. This is safe because the UUID is _also_ unique across all panes. #[derive(Identifiable, Selectable, Queryable)] #[diesel(table_name = terminal_panes)] #[diesel(primary_key(uuid))] pub struct TerminalSession { pub uuid: Vec, } #[derive(Queryable)] pub struct PaneBranch { #[allow(dead_code)] pub id: i32, #[allow(dead_code)] pub pane_node_id: i32, pub horizontal: bool, } #[derive(Queryable)] pub struct PaneNode { pub id: i32, #[allow(dead_code)] pub tab_id: i32, #[allow(dead_code)] pub parent_pane_node_id: Option, pub flex: Option, pub is_leaf: bool, } #[derive(Insertable)] #[diesel(table_name = pane_leaves)] pub struct NewPane { pub pane_node_id: i32, pub kind: String, pub is_focused: bool, pub custom_vertical_tabs_title: Option, } /// The [`pane_leaves::kind`] value for terminal panes. pub const TERMINAL_PANE_KIND: &str = "terminal"; /// The [`pane_leaves::kind`] value for notebook panes. pub const NOTEBOOK_PANE_KIND: &str = "notebook"; /// The [`pane_leaves::kind`] value for EVC panes. pub const ENV_VAR_COLLECTION_PANE_KIND: &str = "env_var_collection"; /// The [`pane_leaves::kind`] value for code panes. pub const CODE_PANE_KIND: &str = "code"; /// The [`pane_leaves::kind`] value for workflow panes. pub const WORKFLOW_PANE_KIND: &str = "workflow"; /// The [`pane_leaves::kind`] value for settings panes. pub const SETTINGS_PANE_KIND: &str = "settings"; /// The [`pane_leaves::kind`] value for AI fact panes /// (where kind name is historical and not worth a migration to change). pub const AI_FACT_PANE_KIND: &str = "ai_memory"; /// The [`pane_leaves::kind`] value for MCP server panes pub const MCP_SERVER_PANE_KIND: &str = "mcp_server"; /// The [`pane_leaves::kind`] value for code review panes. pub const CODE_REVIEW_PANE_KIND: &str = "code_review"; /// The [`pane_leaves::kind`] value for execution profile editor panes. pub const EXECUTION_PROFILE_EDITOR_PANE_KIND: &str = "execution_profile_editor"; /// The [`pane_leaves::kind`] value for the get-started pane. pub const GET_STARTED_PANE_KIND: &str = "get_started"; /// The [`pane_leaves::kind`] value for AI document panes. pub const AI_DOCUMENT_PANE_KIND: &str = "ai_document"; /// The [`pane_leaves::kind`] value for ambient agent (cloud mode) panes. pub const AMBIENT_AGENT_PANE_KIND: &str = "ambient_agent"; #[derive(Insertable)] #[diesel(table_name = terminal_panes)] pub struct NewTerminalPane { pub id: i32, pub uuid: Vec, pub cwd: Option, pub is_active: bool, /// This is serialized JSON data for a ShellLaunchData struct. pub shell_launch_data: Option, /// This is serialized JSON data for an InputConfig struct. pub input_config: Option, pub llm_model_override: Option, pub active_profile_id: Option, /// This is serialized JSON data for a Vec. pub conversation_ids: Option, /// The active conversation ID if the agent view was open in fullscreen mode. pub active_conversation_id: Option, } #[derive(Insertable)] #[diesel(table_name = notebook_panes)] pub struct NewNotebookPane { pub id: i32, pub notebook_id: Option, pub local_path: Option>, } #[derive(Insertable)] #[diesel(table_name = env_var_collection_panes)] pub struct NewEnvVarCollectionPane { pub id: i32, pub env_var_collection_id: Option, } #[derive(Insertable)] #[diesel(table_name = workflow_panes)] pub struct NewWorkflowPane { pub id: i32, pub workflow_id: Option, } #[derive(Insertable)] #[diesel(table_name = code_panes)] pub struct NewCodePane { pub id: i32, pub active_tab_index: i32, pub source_data: Option, } #[derive(Insertable)] #[diesel(table_name = code_pane_tabs)] pub struct NewCodePaneTab { pub code_pane_id: i32, pub tab_index: i32, pub local_path: Option>, } #[derive(Insertable)] #[diesel(table_name = code_review_panes)] pub struct NewCodeReviewPane { pub id: i32, pub terminal_uuid: Vec, pub repo_path: String, } #[derive(Insertable)] #[diesel(table_name = settings_panes)] pub struct NewSettingsPane { pub id: i32, pub current_page: String, } #[derive(Insertable)] #[diesel(table_name = ai_memory_panes)] pub struct NewAIFactPane { pub id: i32, } #[derive(Insertable)] #[diesel(table_name = mcp_server_panes)] pub struct NewMCPServerPane { pub id: i32, } #[derive(Identifiable, Queryable, Selectable)] #[diesel(table_name = ambient_agent_panes)] #[diesel(primary_key(id))] pub struct AmbientAgentPane { pub id: i32, pub kind: String, pub uuid: Vec, pub task_id: Option, } #[derive(Insertable)] #[diesel(table_name = ambient_agent_panes)] pub struct NewAmbientAgentPane { pub id: i32, pub uuid: Vec, pub task_id: Option, } #[derive(Insertable)] #[diesel(table_name = pane_branches)] pub struct NewPaneBranch { pub pane_node_id: i32, pub horizontal: bool, } #[derive(Insertable)] #[diesel(table_name = pane_nodes)] pub struct NewPaneNode { pub tab_id: i32, pub parent_pane_node_id: Option, pub flex: Option, pub is_leaf: bool, } #[derive(Insertable)] #[diesel(table_name = blocks)] pub struct NewBlock<'a> { pub block_id: &'a str, // Note that there is no pane leaf UUID foreign key relationship because there's no good way to // enforce it: when we remove a pane and subsequently create a new snapshot, the old blocks // will now violate the constraint. While sqlite does have deferred constraints, it doesn't // work well with ON DELETE CASCADE (i.e. the cascade happens on the delete, not after the // transaction commit). pub pane_leaf_uuid: Vec, pub stylized_command: &'a Vec, pub stylized_output: &'a Vec, pub pwd: Option<&'a String>, pub git_branch: Option<&'a String>, pub git_branch_name: Option<&'a String>, pub virtual_env: Option<&'a String>, pub conda_env: Option<&'a String>, pub exit_code: i32, pub did_execute: bool, pub is_background: bool, pub completed_ts: Option, pub start_ts: Option, pub ps1: Option<&'a String>, pub rprompt: Option<&'a String>, pub honor_ps1: bool, pub shell: Option<&'a str>, pub user: Option<&'a str>, pub host: Option<&'a str>, pub prompt_snapshot: Option<&'a String>, pub ai_metadata: Option<&'a String>, pub is_local: Option, pub agent_view_visibility: Option, } #[derive(Identifiable, Queryable, Selectable, Associations)] #[diesel(table_name = blocks)] #[diesel(belongs_to(TerminalSession, foreign_key = pane_leaf_uuid))] pub struct Block { pub id: Option, pub pane_leaf_uuid: Vec, pub stylized_command: Vec, pub stylized_output: Vec, pub pwd: Option, pub git_branch: Option, pub git_branch_name: Option, pub virtual_env: Option, pub conda_env: Option, pub exit_code: i32, pub did_execute: bool, pub completed_ts: Option, pub start_ts: Option, pub ps1: Option, pub honor_ps1: bool, pub shell: Option, pub user: Option, pub host: Option, pub is_background: bool, pub rprompt: Option, /// JSON-serialized representation of the Warp prompt snapshot (Context Chips). Note that this /// is different from PS1 and RPROMPT1 pub prompt_snapshot: Option, pub block_id: String, pub ai_metadata: Option, pub is_local: Option, pub agent_view_visibility: Option, } #[derive(Insertable)] #[diesel(table_name = commands)] pub struct NewCommand { pub command: String, pub exit_code: Option, pub start_ts: Option, pub completed_ts: Option, pub pwd: Option, pub shell: Option, pub username: Option, pub hostname: Option, pub session_id: Option, pub git_branch: Option, pub cloud_workflow_id: Option, pub workflow_command: Option, pub is_agent_executed: Option, } #[derive(Identifiable, Queryable, Default, Clone)] #[diesel(table_name = commands)] pub struct Command { pub id: i32, pub command: String, pub exit_code: Option, pub start_ts: Option, pub completed_ts: Option, pub pwd: Option, pub shell: Option, pub username: Option, pub hostname: Option, pub session_id: Option, pub git_branch: Option, pub cloud_workflow_id: Option, pub workflow_command: Option, pub is_agent_executed: Option, } #[derive(Identifiable, Queryable, Insertable)] #[diesel(table_name = user_profiles)] #[diesel(primary_key(firebase_uid))] pub struct UserProfile { pub firebase_uid: String, pub photo_url: String, pub email: String, pub display_name: Option, } #[derive(Insertable)] #[diesel(table_name = cloud_objects_refreshes)] pub struct NewCloudObjectsRefresh { pub time_of_next_refresh: NaiveDateTime, } #[derive(Identifiable, Queryable)] #[diesel(table_name = cloud_objects_refreshes)] pub struct CloudObjectsRefresh { pub id: i32, pub time_of_next_refresh: NaiveDateTime, } #[derive(Insertable)] #[diesel(table_name = object_actions)] pub struct NewPersistedObjectAction { pub hashed_object_id: String, pub timestamp: Option, pub action: String, pub data: Option, pub count: Option, pub oldest_timestamp: Option, pub latest_timestamp: Option, pub pending: Option, pub processed_at_timestamp: Option, } #[derive(Identifiable, Queryable, Insertable, Debug)] #[diesel(table_name = object_actions)] pub struct PersistedObjectAction { pub id: i32, pub hashed_object_id: String, pub timestamp: Option, pub action: String, pub data: Option, pub count: Option, pub oldest_timestamp: Option, pub latest_timestamp: Option, pub pending: Option, pub processed_at_timestamp: Option, } #[derive(Insertable, Queryable)] pub struct ServerExperiment { pub experiment: String, } #[derive(Insertable)] #[diesel(table_name = server_experiments)] pub struct NewServerExperiment { pub experiment: String, } #[derive(Debug, Insertable)] #[diesel(table_name = current_user_information)] pub struct CurrentUserInformation { pub email: String, } #[derive(Debug, Insertable, Queryable, AsChangeset)] #[diesel(table_name = mcp_environment_variables)] pub struct MCPEnvironmentVariables { pub mcp_server_uuid: Vec, pub environment_variables: String, } #[derive(Debug, Insertable, Queryable)] #[diesel(table_name = active_mcp_servers)] pub struct ActiveMCPServer { pub id: i32, pub mcp_server_uuid: String, } #[derive(Debug, Insertable)] #[diesel(table_name = active_mcp_servers)] pub struct NewActiveMCPServer { pub mcp_server_uuid: String, } // Queryable structs for reading from the database #[derive(Debug, PartialEq, Default, Queryable, Selectable, Clone)] #[diesel(table_name = agent_conversations)] #[diesel(primary_key(id))] pub struct AgentConversationRecord { pub id: i32, pub conversation_id: String, pub conversation_data: String, pub last_modified_at: NaiveDateTime, } #[derive(Debug, PartialEq, Queryable, Selectable)] #[diesel(table_name = agent_tasks)] #[diesel(primary_key(id))] pub struct AgentTaskRecord { pub id: i32, pub conversation_id: String, pub task_id: String, pub task: Vec, pub last_modified_at: NaiveDateTime, } #[derive(Debug, PartialEq, Queryable, Selectable, Clone)] #[diesel(table_name = ai_document_panes)] #[diesel(primary_key(id))] pub struct AIDocumentPane { pub id: i32, pub kind: String, pub document_id: String, pub version: i32, pub content: Option, pub title: Option, } #[derive(Debug, Insertable)] #[diesel(table_name = ai_document_panes)] pub struct NewAIDocumentPane { pub id: i32, pub document_id: String, pub version: i32, pub content: Option, pub title: Option, } #[derive(Debug, PartialEq, Default, Clone)] pub struct AgentConversation { pub conversation: AgentConversationRecord, pub tasks: Vec, } impl AgentConversation { /// Returns `true` if the conversation is restorable. /// /// A conversation is restorable if: /// - It contains a single task or fewer, OR /// - It has exactly one parentless (root) task, OR /// - It has multiple parentless tasks but exactly one of them has /// non-empty `messages`. This permits restoring conversations whose /// persisted state was corrupted by the pre-QUALITY-774 optimistic-root /// writer bug, where a stub root row co-existed with the real server /// root row. `AIConversation::new_restored` deterministically picks /// the real root in that shape via its restore-side dedupe. /// /// Non-root tasks need not be validated here: any task that does not /// match the parentless predicate has, by construction, a non-empty /// `parent_task_id`. pub fn is_restorable(&self) -> bool { if self.tasks.len() <= 1 { return true; } // Find parentless (root) tasks - tasks with no dependencies or with an // empty parent_task_id. let root_tasks: Vec<_> = self .tasks .iter() .filter(|task| { task.dependencies .as_ref() .map(|deps| deps.parent_task_id.is_empty()) .unwrap_or(true) }) .collect(); match root_tasks.len() { // Malformed: no parentless task means no root to anchor restore on. 0 => false, // Single root: the normal happy path. 1 => true, // Multi-root: only permit the specific [stub + real] shape // produced by the pre-QUALITY-774 optimistic-root writer bug, // where exactly one parentless row carries the real conversation // content. The restore-side dedupe in // `AIConversation::new_restored` will pick that real root. _ => root_tasks.iter().filter(|t| !t.messages.is_empty()).count() == 1, } } } #[derive(Debug, Serialize, Clone, Copy, PartialEq, Eq, Default)] pub enum PersistedAutoexecuteMode { #[default] RespectUserSettings, RunToCompletion, } impl<'de> Deserialize<'de> for PersistedAutoexecuteMode { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let value = String::deserialize(deserializer)?; Ok(match value.as_str() { "RespectUserSettings" => Self::RespectUserSettings, "RunToCompletion" => Self::RunToCompletion, _ => Self::default(), }) } } fn is_false(value: &bool) -> bool { !*value } /// Backend responsible for executing an agent conversation. /// /// Existing persisted conversations predate this field and therefore default /// to Galaxy's native model-provider path. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "snake_case")] pub enum AgentBackend { #[default] Provider, Acp(AcpConversationData), } impl AgentBackend { pub fn is_provider(&self) -> bool { matches!(self, Self::Provider) } /// Copies the backend identity for a locally forked conversation without /// sharing an agent-owned session between two Galaxy conversations. pub fn for_fork(&self) -> Self { match self { Self::Provider => Self::Provider, Self::Acp(acp) => Self::Acp(AcpConversationData { agent_id: acp.agent_id.clone(), launch_fingerprint: acp.launch_fingerprint.clone(), session_id: None, config_values: acp.config_values.clone(), }), } } } /// Persisted identity for a local Agent Client Protocol conversation. /// /// Process launch details remain device-local settings. The non-secret launch /// fingerprint prevents an agent-owned session ID from being handed to a /// different executable after those settings change. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct AcpConversationData { #[serde(default)] pub agent_id: String, #[serde(default, skip_serializing_if = "String::is_empty")] pub launch_fingerprint: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub session_id: Option, /// ACP session configuration values selected for this conversation. /// Values are opaque to Galaxy and are keyed by the agent-provided option ID. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub config_values: BTreeMap, } // Serializes to `conversation_data` column in `agent_conversations`. #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct AgentConversationData { #[serde(default, skip_serializing_if = "AgentBackend::is_provider")] pub agent_backend: AgentBackend, pub server_conversation_token: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub conversation_usage_metadata: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub reverted_action_ids: Option>, /// The server conversation ID of the source conversation if this conversation was forked. #[serde(default, skip_serializing_if = "Option::is_none")] pub forked_from_server_conversation_token: Option, /// Serialized Vec for local artifact tracking. #[serde(default, skip_serializing_if = "Option::is_none")] pub artifacts_json: Option, /// Server-side identifier of the parent agent that spawned this child. /// In v1 this is the parent's conversation token; in v2 it is the parent's run_id. #[serde(default, skip_serializing_if = "Option::is_none")] pub parent_agent_id: Option, /// The display name for this agent, assigned by the orchestrator. #[serde(default, skip_serializing_if = "Option::is_none")] pub agent_name: Option, /// Harness type used to render the child agent's shared icon in orchestration UI. #[serde( default, alias = "orchestration_avatar_id", skip_serializing_if = "Option::is_none" )] pub orchestration_harness_type: Option, /// The local conversation ID of the parent conversation. #[serde(default, skip_serializing_if = "Option::is_none")] pub parent_conversation_id: Option, /// True when this conversation is a parent-side placeholder for a child /// agent executing on a remote worker. #[serde(default, skip_serializing_if = "is_false")] pub is_remote_child: bool, /// Legacy marker that previously recorded whether the root task was still /// optimistic when this conversation was persisted. Retained on the struct /// for backward-compatible deserialization of rows written by older builds; /// new writes always emit `None` and restore code ignores the value. /// // TODO: Remove this field once no live local DBs still contain // `Some(true)` rows that legacy code paths might trip over. #[serde(default, skip_serializing_if = "Option::is_none")] pub root_task_is_optimistic: Option, /// The server-assigned run identifier (`ai_tasks.id`) for v2 orchestration. /// For local agents this arrives via StreamInit; for cloud agents it will /// come from SpawnAgentResponse once the local→cloud spawn path is wired. #[serde(default, skip_serializing_if = "Option::is_none")] pub run_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub autoexecute_override: Option, /// The last event sequence number from the v2 orchestration event log /// that this conversation has observed. Used on restore to resume event /// delivery without re-delivering already-processed events. #[serde(default, skip_serializing_if = "Option::is_none")] pub last_event_sequence: Option, /// Whether the user has pinned this child agent in the orchestration /// pill bar. Orchestrator conversations always serialize as `false`. #[serde(default, skip_serializing_if = "is_false")] pub pinned: bool, /// Progressive summary of older conversation messages. #[serde(default, skip_serializing_if = "Option::is_none")] pub progressive_summary: Option, /// Number of messages that were summarized into progressive_summary. #[serde(default)] pub messages_summarized_up_to: usize, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct AIAgentActionId(pub String); pub type TokenUsageCategory = String; pub const PRIMARY_AGENT_CATEGORY: &str = "primary_agent"; pub const FULL_TERMINAL_USE_CATEGORY: &str = "full_terminal_use"; pub fn token_usage_category_display_name(category: &str) -> String { category .split('_') .map(|word| { let mut chars = word.chars(); match chars.next() { Some(first) => first.to_uppercase().chain(chars).collect(), None => String::new(), } }) .collect::>() .join(" ") } #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct ModelTokenUsage { /// Identifier used for both display and replay. For warp/byok rows this is the /// server-known model id; for custom endpoint rows this is the resolved alias /// (or fallback label) — the upstream `config_key` is translated into this /// label once at ingestion time and is not retained separately. pub model_id: String, /// Alias for backward compat: old persisted data used `total_tokens` for warp usage. #[serde(default, alias = "total_tokens")] pub warp_tokens: u32, #[serde(default)] pub byok_tokens: u32, #[serde(default)] pub custom_endpoint_tokens: u32, #[serde(default)] pub warp_token_usage_by_category: HashMap, #[serde(default)] pub byok_token_usage_by_category: HashMap, #[serde(default)] pub custom_endpoint_token_usage_by_category: HashMap, } impl ModelTokenUsage { #[allow(deprecated)] fn to_proto_usage( &self, total_tokens: u32, usage_by_category: &HashMap, ) -> Option<(String, stream_finished::ModelTokenUsage)> { if total_tokens == 0 { return None; } Some(( self.model_id.clone(), stream_finished::ModelTokenUsage { model_id: self.model_id.clone(), total_tokens, token_usage_by_category: usage_by_category .iter() .map(|(cat, tokens)| (cat.clone(), *tokens)) .collect(), }, )) } pub fn to_proto_warp_usage(&self) -> Option<(String, stream_finished::ModelTokenUsage)> { self.to_proto_usage(self.warp_tokens, &self.warp_token_usage_by_category) } pub fn to_proto_byok_usage(&self) -> Option<(String, stream_finished::ModelTokenUsage)> { self.to_proto_usage(self.byok_tokens, &self.byok_token_usage_by_category) } #[allow(deprecated)] pub fn to_proto_custom_endpoint_usage( &self, ) -> Option<(String, stream_finished::ModelTokenUsage)> { if self.custom_endpoint_tokens == 0 { return None; } Some(( self.model_id.clone(), stream_finished::ModelTokenUsage { model_id: self.model_id.clone(), total_tokens: self.custom_endpoint_tokens, token_usage_by_category: self .custom_endpoint_token_usage_by_category .iter() .map(|(cat, tokens)| (cat.clone(), *tokens)) .collect(), }, )) } #[allow(deprecated)] pub fn to_proto_combined(&self) -> stream_finished::ModelTokenUsage { stream_finished::ModelTokenUsage { model_id: self.model_id.clone(), total_tokens: self.warp_tokens + self.byok_tokens + self.custom_endpoint_tokens, token_usage_by_category: self .warp_token_usage_by_category .iter() .chain(self.byok_token_usage_by_category.iter()) .chain(self.custom_endpoint_token_usage_by_category.iter()) .fold(HashMap::new(), |mut acc, (cat, tokens)| { *acc.entry(cat.clone()).or_insert(0) += tokens; acc }), } } } #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct ToolCallStats { pub count: i32, } impl From<&ToolCallStats> for stream_finished::ToolCallStats { fn from(stats: &ToolCallStats) -> Self { Self { count: stats.count } } } impl From<&stream_finished::ToolCallStats> for ToolCallStats { fn from(tool_call_stats: &stream_finished::ToolCallStats) -> Self { Self { count: tool_call_stats.count, } } } #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct RunCommandStats { pub count: i32, pub commands_executed: i32, } impl From<&RunCommandStats> for stream_finished::RunCommandStats { fn from(stats: &RunCommandStats) -> Self { Self { count: stats.count, command_executed: stats.commands_executed, } } } impl From<&stream_finished::RunCommandStats> for RunCommandStats { fn from(stats: &stream_finished::RunCommandStats) -> Self { Self { count: stats.count, commands_executed: stats.command_executed, } } } #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct ApplyFileDiffStats { pub count: i32, pub lines_added: i32, pub lines_removed: i32, pub files_changed: i32, } impl From<&ApplyFileDiffStats> for stream_finished::ApplyFileDiffStats { fn from(stats: &ApplyFileDiffStats) -> Self { Self { count: stats.count, lines_added: stats.lines_added, lines_removed: stats.lines_removed, files_changed: stats.files_changed, } } } impl From<&stream_finished::ApplyFileDiffStats> for ApplyFileDiffStats { fn from(file_diff_stats: &stream_finished::ApplyFileDiffStats) -> Self { Self { count: file_diff_stats.count, lines_added: file_diff_stats.lines_added, lines_removed: file_diff_stats.lines_removed, files_changed: file_diff_stats.files_changed, } } } #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct ToolUsageMetadata { pub run_command_stats: RunCommandStats, pub read_files_stats: ToolCallStats, pub search_codebase_stats: ToolCallStats, pub grep_stats: ToolCallStats, pub file_glob_stats: ToolCallStats, pub apply_file_diff_stats: ApplyFileDiffStats, pub write_to_long_running_shell_command_stats: ToolCallStats, pub read_mcp_resource_stats: ToolCallStats, pub call_mcp_tool_stats: ToolCallStats, pub suggest_plan_stats: ToolCallStats, pub suggest_create_plan_stats: ToolCallStats, pub read_shell_command_output_stats: ToolCallStats, pub use_computer_stats: ToolCallStats, } impl ToolUsageMetadata { pub fn total_tool_calls(&self) -> i32 { self.run_command_stats.count + self.read_files_stats.count + self.search_codebase_stats.count + self.grep_stats.count + self.file_glob_stats.count + self.write_to_long_running_shell_command_stats.count + self.read_mcp_resource_stats.count + self.call_mcp_tool_stats.count + self.suggest_plan_stats.count + self.suggest_create_plan_stats.count + self.apply_file_diff_stats.count + self.read_shell_command_output_stats.count + self.use_computer_stats.count } } impl From<&ToolUsageMetadata> for stream_finished::ToolUsageMetadata { fn from(metadata: &ToolUsageMetadata) -> Self { Self { run_command_stats: Some((&metadata.run_command_stats).into()), read_files_stats: Some((&metadata.read_files_stats).into()), search_codebase_stats: Some((&metadata.search_codebase_stats).into()), grep_stats: Some((&metadata.grep_stats).into()), file_glob_stats: Some((&metadata.file_glob_stats).into()), apply_file_diff_stats: Some((&metadata.apply_file_diff_stats).into()), write_to_long_running_shell_command_stats: Some( (&metadata.write_to_long_running_shell_command_stats).into(), ), read_mcp_resource_stats: Some((&metadata.read_mcp_resource_stats).into()), call_mcp_tool_stats: Some((&metadata.call_mcp_tool_stats).into()), suggest_plan_stats: Some((&metadata.suggest_plan_stats).into()), suggest_create_plan_stats: Some((&metadata.suggest_create_plan_stats).into()), read_shell_command_output_stats: Some( (&metadata.read_shell_command_output_stats).into(), ), use_computer_stats: Some((&metadata.use_computer_stats).into()), } } } impl From<&stream_finished::ToolUsageMetadata> for ToolUsageMetadata { fn from(tool_usage_metadata: &stream_finished::ToolUsageMetadata) -> Self { let convert = |opt: &Option<_>| opt.as_ref().map(Into::into).unwrap_or_default(); Self { run_command_stats: tool_usage_metadata .run_command_stats .as_ref() .map(Into::into) .unwrap_or_default(), read_files_stats: convert(&tool_usage_metadata.read_files_stats), search_codebase_stats: convert(&tool_usage_metadata.search_codebase_stats), grep_stats: convert(&tool_usage_metadata.grep_stats), file_glob_stats: convert(&tool_usage_metadata.file_glob_stats), apply_file_diff_stats: tool_usage_metadata .apply_file_diff_stats .as_ref() .map(Into::into) .unwrap_or_default(), write_to_long_running_shell_command_stats: convert( &tool_usage_metadata.write_to_long_running_shell_command_stats, ), read_mcp_resource_stats: convert(&tool_usage_metadata.read_mcp_resource_stats), call_mcp_tool_stats: convert(&tool_usage_metadata.call_mcp_tool_stats), suggest_plan_stats: convert(&tool_usage_metadata.suggest_plan_stats), suggest_create_plan_stats: convert(&tool_usage_metadata.suggest_create_plan_stats), read_shell_command_output_stats: convert( &tool_usage_metadata.read_shell_command_output_stats, ), use_computer_stats: convert(&tool_usage_metadata.use_computer_stats), } } } /// The kind of a context-window segment, mirroring the proto /// `ContextWindowSegmentType` enum. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "snake_case")] pub enum ContextWindowSegmentType { #[default] Unknown, SystemPrompt, ToolDefinitions, ConversationHistory, LatestInput, Images, Other, } impl ContextWindowSegmentType { /// Snake-case identifier used for display-name lookup. pub fn as_str(&self) -> &'static str { match self { ContextWindowSegmentType::Unknown => "unknown", ContextWindowSegmentType::SystemPrompt => "system_prompt", ContextWindowSegmentType::ToolDefinitions => "tool_definitions", ContextWindowSegmentType::ConversationHistory => "conversation_history", ContextWindowSegmentType::LatestInput => "latest_input", ContextWindowSegmentType::Images => "images", ContextWindowSegmentType::Other => "other", } } } impl From for ContextWindowSegmentType { fn from(value: i32) -> Self { match stream_finished::ContextWindowSegmentType::try_from(value) { Ok(stream_finished::ContextWindowSegmentType::SystemPrompt) => Self::SystemPrompt, Ok(stream_finished::ContextWindowSegmentType::ToolDefinitions) => Self::ToolDefinitions, Ok(stream_finished::ContextWindowSegmentType::ConversationHistory) => { Self::ConversationHistory } Ok(stream_finished::ContextWindowSegmentType::LatestInput) => Self::LatestInput, Ok(stream_finished::ContextWindowSegmentType::Images) => Self::Images, Ok(stream_finished::ContextWindowSegmentType::Other) => Self::Other, // Unknown (0) and any unrecognized value map to Unknown. _ => Self::Unknown, } } } impl From for i32 { fn from(value: ContextWindowSegmentType) -> Self { match value { ContextWindowSegmentType::Unknown => { stream_finished::ContextWindowSegmentType::Unknown as i32 } ContextWindowSegmentType::SystemPrompt => { stream_finished::ContextWindowSegmentType::SystemPrompt as i32 } ContextWindowSegmentType::ToolDefinitions => { stream_finished::ContextWindowSegmentType::ToolDefinitions as i32 } ContextWindowSegmentType::ConversationHistory => { stream_finished::ContextWindowSegmentType::ConversationHistory as i32 } ContextWindowSegmentType::LatestInput => { stream_finished::ContextWindowSegmentType::LatestInput as i32 } ContextWindowSegmentType::Images => { stream_finished::ContextWindowSegmentType::Images as i32 } ContextWindowSegmentType::Other => { stream_finished::ContextWindowSegmentType::Other as i32 } } } } /// A single portion of the context window, described by its kind and an /// estimated token count. Segment token counts add up to the token total /// represented by `context_window_usage`. #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct ContextWindowSegment { pub segment_type: ContextWindowSegmentType, /// Estimated number of tokens this segment occupies in the context window. pub token_count: u32, } impl From<&stream_finished::ContextWindowSegment> for ContextWindowSegment { fn from(segment: &stream_finished::ContextWindowSegment) -> Self { Self { segment_type: segment.segment_type.into(), token_count: segment.token_count, } } } impl From<&ContextWindowSegment> for stream_finished::ContextWindowSegment { fn from(segment: &ContextWindowSegment) -> Self { Self { segment_type: segment.segment_type.into(), token_count: segment.token_count, } } } #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct ConversationUsageMetadata { pub was_summarized: bool, pub context_window_usage: f32, pub credits_spent: f32, #[serde(default)] pub platform_credits_spent: f32, #[serde(default)] pub credits_spent_for_last_block: Option, #[serde(default)] pub token_usage: Vec, #[serde(default)] pub tool_usage_metadata: ToolUsageMetadata, #[serde(default)] pub context_window_segments: Vec, #[serde(default)] pub total_cache_read_tokens: u32, #[serde(default)] pub total_cache_write_tokens: u32, #[serde(default)] pub total_cache_miss_tokens: u32, #[serde(default)] pub total_cost_cents: f32, } impl ConversationUsageMetadata { pub fn total_tool_calls(&self) -> i32 { self.tool_usage_metadata.total_tool_calls() } } #[derive(Debug, Insertable)] #[diesel(table_name = ignored_suggestions)] pub struct NewIgnoredSuggestion { pub suggestion: String, pub suggestion_type: String, } #[derive(Insertable, AsChangeset)] #[diesel(table_name = mcp_server_installations)] #[diesel(treat_none_as_null = true)] #[diesel(primary_key(id))] pub struct NewMCPServerInstallation { pub id: String, pub templatable_mcp_server: String, pub template_version_ts: NaiveDateTime, pub variable_values: String, pub restore_running: bool, pub last_modified_at: NaiveDateTime, } #[cfg(test)] #[path = "model_tests.rs"] mod tests; #[derive(Insertable)] #[diesel(table_name = panels)] pub struct NewPanel { pub tab_id: i32, pub left_panel: Option, pub right_panel: Option, } #[derive(Identifiable, Queryable, Selectable)] #[diesel(table_name = panels)] pub struct Panel { pub id: i32, pub tab_id: i32, pub left_panel: Option, pub right_panel: Option, }