2024-06-21 22:54:03 +00:00
|
|
|
mod agent;
|
2023-11-27 06:04:50 +00:00
|
|
|
mod input;
|
2023-03-09 11:10:02 +00:00
|
|
|
mod role;
|
2023-10-28 13:39:17 +00:00
|
|
|
mod session;
|
2023-03-09 02:39:28 +00:00
|
|
|
|
2024-07-24 12:33:52 +00:00
|
|
|
pub use self::agent::{list_agents, Agent};
|
2024-06-11 03:00:12 +00:00
|
|
|
pub use self::input::Input;
|
2024-08-30 11:26:57 +00:00
|
|
|
pub use self::role::{Role, RoleLike, BUILTIN_ROLES, CODE_ROLE, EXPLAIN_SHELL_ROLE, SHELL_ROLE};
|
2024-06-11 03:00:12 +00:00
|
|
|
use self::session::Session;
|
2023-03-09 02:39:28 +00:00
|
|
|
|
2023-10-31 08:50:08 +00:00
|
|
|
use crate::client::{
|
2024-06-24 23:50:19 +00:00
|
|
|
create_client_config, list_chat_models, list_client_types, list_reranker_models, ClientConfig,
|
2024-06-20 22:00:26 +00:00
|
|
|
Model, OPENAI_COMPATIBLE_PLATFORMS,
|
2023-10-31 08:50:08 +00:00
|
|
|
};
|
2024-07-05 11:49:08 +00:00
|
|
|
use crate::function::{FunctionDeclaration, Functions, ToolResult};
|
2024-06-11 03:00:12 +00:00
|
|
|
use crate::rag::Rag;
|
2023-11-02 01:53:54 +00:00
|
|
|
use crate::render::{MarkdownRender, RenderOptions};
|
2024-06-11 06:01:45 +00:00
|
|
|
use crate::utils::*;
|
2023-03-09 00:11:38 +00:00
|
|
|
|
2023-03-09 02:39:28 +00:00
|
|
|
use anyhow::{anyhow, bail, Context, Result};
|
2024-07-05 11:49:08 +00:00
|
|
|
use indexmap::IndexMap;
|
2024-08-30 11:26:57 +00:00
|
|
|
use inquire::{validator::Validation, Confirm, Select, Text};
|
2023-03-11 13:45:34 +00:00
|
|
|
use parking_lot::RwLock;
|
2023-03-09 11:10:02 +00:00
|
|
|
use serde::Deserialize;
|
2024-04-30 04:52:58 +00:00
|
|
|
use serde_json::json;
|
2024-07-21 09:24:04 +00:00
|
|
|
use simplelog::LevelFilter;
|
2024-03-03 06:52:15 +00:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2023-03-03 01:15:58 +00:00
|
|
|
use std::{
|
2023-03-03 01:38:53 +00:00
|
|
|
env,
|
2023-10-28 13:39:17 +00:00
|
|
|
fs::{create_dir_all, read_dir, read_to_string, remove_file, File, OpenOptions},
|
2024-06-05 22:08:41 +00:00
|
|
|
io::Write,
|
2023-03-03 01:15:58 +00:00
|
|
|
path::{Path, PathBuf},
|
2024-06-26 10:56:26 +00:00
|
|
|
process,
|
2023-03-05 14:51:29 +00:00
|
|
|
sync::Arc,
|
2023-03-03 01:15:58 +00:00
|
|
|
};
|
2023-10-31 01:06:41 +00:00
|
|
|
use syntect::highlighting::ThemeSet;
|
|
|
|
|
|
|
|
/// Monokai Extended
|
|
|
|
const DARK_THEME: &[u8] = include_bytes!("../../assets/monokai-extended.theme.bin");
|
|
|
|
const LIGHT_THEME: &[u8] = include_bytes!("../../assets/monokai-extended-light.theme.bin");
|
2023-03-02 11:52:11 +00:00
|
|
|
|
2023-03-03 04:43:34 +00:00
|
|
|
const CONFIG_FILE_NAME: &str = "config.yaml";
|
2024-08-30 11:26:57 +00:00
|
|
|
const ROLES_DIR_NAME: &str = "roles";
|
2024-07-04 06:40:52 +00:00
|
|
|
const ENV_FILE_NAME: &str = ".env";
|
2023-10-28 13:39:17 +00:00
|
|
|
const MESSAGES_FILE_NAME: &str = "messages.md";
|
|
|
|
const SESSIONS_DIR_NAME: &str = "sessions";
|
2024-06-05 01:02:23 +00:00
|
|
|
const RAGS_DIR_NAME: &str = "rags";
|
2024-05-18 11:06:21 +00:00
|
|
|
const FUNCTIONS_DIR_NAME: &str = "functions";
|
2024-06-11 03:00:12 +00:00
|
|
|
const FUNCTIONS_FILE_NAME: &str = "functions.json";
|
2024-06-12 00:56:56 +00:00
|
|
|
const FUNCTIONS_BIN_DIR_NAME: &str = "bin";
|
2024-06-23 15:09:47 +00:00
|
|
|
const AGENTS_DIR_NAME: &str = "agents";
|
2024-07-09 13:22:51 +00:00
|
|
|
const AGENT_VARIABLES_FILE_NAME: &str = "variables.yaml";
|
2024-06-11 03:00:12 +00:00
|
|
|
|
|
|
|
pub const TEMP_ROLE_NAME: &str = "%%";
|
|
|
|
pub const TEMP_RAG_NAME: &str = "temp";
|
|
|
|
pub const TEMP_SESSION_NAME: &str = "temp";
|
2023-10-28 13:39:17 +00:00
|
|
|
|
2023-11-01 02:28:54 +00:00
|
|
|
const CLIENTS_FIELD: &str = "clients";
|
|
|
|
|
2024-04-23 06:32:06 +00:00
|
|
|
const SUMMARIZE_PROMPT: &str =
|
|
|
|
"Summarize the discussion briefly in 200 words or less to use as a prompt for future context.";
|
|
|
|
const SUMMARY_PROMPT: &str = "This is a summary of the chat history as a recap: ";
|
2024-06-05 01:02:23 +00:00
|
|
|
|
2024-06-14 11:12:18 +00:00
|
|
|
const RAG_TEMPLATE: &str = r#"Use the following context as your learned knowledge, inside <context></context> XML tags.
|
2024-06-19 04:15:54 +00:00
|
|
|
<context>
|
|
|
|
__CONTEXT__
|
|
|
|
</context>
|
2024-06-05 01:02:23 +00:00
|
|
|
|
2024-06-19 04:15:54 +00:00
|
|
|
When answer to user:
|
|
|
|
- If you don't know, just say that you don't know.
|
|
|
|
- If you don't know when you are not sure, ask for clarification.
|
|
|
|
Avoid mentioning that you obtained the information from the context.
|
|
|
|
And answer according to the language of the user's question.
|
2024-06-14 11:12:18 +00:00
|
|
|
|
2024-06-19 04:15:54 +00:00
|
|
|
Given the context information, answer the query.
|
|
|
|
Query: __INPUT__"#;
|
2024-06-05 01:02:23 +00:00
|
|
|
|
2024-06-23 11:31:29 +00:00
|
|
|
const LEFT_PROMPT: &str = "{color.green}{?session {?agent {agent}>}{session}{?role /}}{!session {?agent {agent}>}}{role}{?rag @{rag}}{color.cyan}{?session )}{!session >}{color.reset} ";
|
2024-04-23 06:32:06 +00:00
|
|
|
const RIGHT_PROMPT: &str = "{color.purple}{?session {?consume_tokens {consume_tokens}({consume_percent}%)}{!consume_tokens {consume_tokens}}}{color.reset}";
|
|
|
|
|
2023-03-02 11:52:11 +00:00
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
2023-03-11 03:26:24 +00:00
|
|
|
#[serde(default)]
|
2023-03-02 11:52:11 +00:00
|
|
|
pub struct Config {
|
2023-11-02 22:52:57 +00:00
|
|
|
#[serde(rename(serialize = "model", deserialize = "model"))]
|
2024-05-14 04:43:16 +00:00
|
|
|
#[serde(default)]
|
|
|
|
pub model_id: String,
|
2024-03-27 00:11:00 +00:00
|
|
|
pub temperature: Option<f64>,
|
2024-04-24 08:12:38 +00:00
|
|
|
pub top_p: Option<f64>,
|
2024-06-23 14:09:47 +00:00
|
|
|
|
2023-11-07 23:08:48 +00:00
|
|
|
pub dry_run: bool,
|
2024-07-28 00:56:00 +00:00
|
|
|
pub stream: bool,
|
2023-03-03 01:38:53 +00:00
|
|
|
pub save: bool,
|
2024-07-27 06:52:13 +00:00
|
|
|
pub keybindings: String,
|
2024-08-01 07:01:34 +00:00
|
|
|
pub editor: Option<String>,
|
2023-10-30 02:07:01 +00:00
|
|
|
pub wrap: Option<String>,
|
|
|
|
pub wrap_code: bool,
|
2024-06-23 14:09:47 +00:00
|
|
|
|
2024-04-18 10:22:15 +00:00
|
|
|
pub prelude: Option<String>,
|
2024-06-11 07:44:35 +00:00
|
|
|
pub repl_prelude: Option<String>,
|
2024-06-23 14:09:47 +00:00
|
|
|
pub agent_prelude: Option<String>,
|
|
|
|
|
|
|
|
pub save_session: Option<bool>,
|
|
|
|
pub compress_threshold: usize,
|
|
|
|
pub summarize_prompt: Option<String>,
|
|
|
|
pub summary_prompt: Option<String>,
|
|
|
|
|
2024-06-11 07:44:35 +00:00
|
|
|
pub function_calling: bool,
|
2024-07-05 11:49:08 +00:00
|
|
|
pub mapping_tools: IndexMap<String, String>,
|
|
|
|
pub use_tools: Option<String>,
|
2024-06-23 14:09:47 +00:00
|
|
|
|
2024-06-14 11:12:18 +00:00
|
|
|
pub rag_embedding_model: Option<String>,
|
2024-06-24 23:50:19 +00:00
|
|
|
pub rag_reranker_model: Option<String>,
|
2024-06-23 14:09:47 +00:00
|
|
|
pub rag_top_k: usize,
|
2024-06-14 11:12:18 +00:00
|
|
|
pub rag_chunk_size: Option<usize>,
|
|
|
|
pub rag_chunk_overlap: Option<usize>,
|
2024-06-20 22:00:26 +00:00
|
|
|
pub rag_min_score_vector_search: f32,
|
2024-06-20 23:22:43 +00:00
|
|
|
pub rag_min_score_keyword_search: f32,
|
2024-06-20 22:00:26 +00:00
|
|
|
pub rag_min_score_rerank: f32,
|
2024-07-27 06:52:13 +00:00
|
|
|
pub rag_template: Option<String>,
|
|
|
|
|
2024-06-26 00:18:58 +00:00
|
|
|
#[serde(default)]
|
2024-06-27 22:24:20 +00:00
|
|
|
pub document_loaders: HashMap<String, String>,
|
2024-06-23 14:09:47 +00:00
|
|
|
|
|
|
|
pub highlight: bool,
|
|
|
|
pub light_theme: bool,
|
2024-04-23 06:32:06 +00:00
|
|
|
pub left_prompt: Option<String>,
|
|
|
|
pub right_prompt: Option<String>,
|
2024-06-23 14:09:47 +00:00
|
|
|
|
2023-10-26 08:42:54 +00:00
|
|
|
pub clients: Vec<ClientConfig>,
|
2024-06-23 14:09:47 +00:00
|
|
|
|
2023-03-09 07:30:39 +00:00
|
|
|
#[serde(skip)]
|
2023-03-05 14:51:29 +00:00
|
|
|
pub role: Option<Role>,
|
2023-03-09 07:30:39 +00:00
|
|
|
#[serde(skip)]
|
2023-10-28 13:39:17 +00:00
|
|
|
pub session: Option<Session>,
|
2023-03-16 09:02:09 +00:00
|
|
|
#[serde(skip)]
|
2024-06-05 01:02:23 +00:00
|
|
|
pub rag: Option<Arc<Rag>>,
|
|
|
|
#[serde(skip)]
|
2024-06-21 22:54:03 +00:00
|
|
|
pub agent: Option<Agent>,
|
2024-06-11 03:00:12 +00:00
|
|
|
#[serde(skip)]
|
2023-11-02 22:52:57 +00:00
|
|
|
pub model: Model,
|
2023-10-28 13:39:17 +00:00
|
|
|
#[serde(skip)]
|
2024-06-11 03:00:12 +00:00
|
|
|
pub functions: Functions,
|
2024-05-18 11:06:21 +00:00
|
|
|
#[serde(skip)]
|
2024-04-23 23:16:56 +00:00
|
|
|
pub working_mode: WorkingMode,
|
2024-03-27 02:25:42 +00:00
|
|
|
#[serde(skip)]
|
2024-06-17 05:19:00 +00:00
|
|
|
pub last_message: Option<(Input, String)>,
|
2023-03-02 11:52:11 +00:00
|
|
|
}
|
|
|
|
|
2023-03-11 03:26:24 +00:00
|
|
|
impl Default for Config {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
2024-05-14 04:43:16 +00:00
|
|
|
model_id: Default::default(),
|
2024-03-27 00:11:00 +00:00
|
|
|
temperature: None,
|
2024-04-24 08:12:38 +00:00
|
|
|
top_p: None,
|
2024-06-23 14:09:47 +00:00
|
|
|
|
2023-03-11 03:26:24 +00:00
|
|
|
dry_run: false,
|
2024-07-28 00:56:00 +00:00
|
|
|
stream: true,
|
2024-06-23 14:09:47 +00:00
|
|
|
save: false,
|
2024-07-27 06:52:13 +00:00
|
|
|
keybindings: "emacs".into(),
|
2024-08-01 07:01:34 +00:00
|
|
|
editor: None,
|
2023-10-30 02:07:01 +00:00
|
|
|
wrap: None,
|
|
|
|
wrap_code: false,
|
2024-06-23 14:09:47 +00:00
|
|
|
|
2024-04-18 10:22:15 +00:00
|
|
|
prelude: None,
|
2024-06-11 07:44:35 +00:00
|
|
|
repl_prelude: None,
|
2024-06-23 14:09:47 +00:00
|
|
|
agent_prelude: None,
|
|
|
|
|
2024-07-27 06:52:13 +00:00
|
|
|
save_session: None,
|
|
|
|
compress_threshold: 4000,
|
|
|
|
summarize_prompt: None,
|
|
|
|
summary_prompt: None,
|
|
|
|
|
2024-07-02 11:06:18 +00:00
|
|
|
function_calling: true,
|
2024-07-05 11:49:08 +00:00
|
|
|
mapping_tools: Default::default(),
|
|
|
|
use_tools: None,
|
2024-06-23 14:09:47 +00:00
|
|
|
|
2024-06-14 11:12:18 +00:00
|
|
|
rag_embedding_model: None,
|
2024-06-24 23:50:19 +00:00
|
|
|
rag_reranker_model: None,
|
2024-06-23 14:09:47 +00:00
|
|
|
rag_top_k: 4,
|
2024-06-14 11:12:18 +00:00
|
|
|
rag_chunk_size: None,
|
|
|
|
rag_chunk_overlap: None,
|
2024-06-20 22:00:26 +00:00
|
|
|
rag_min_score_vector_search: 0.0,
|
2024-06-20 23:22:43 +00:00
|
|
|
rag_min_score_keyword_search: 0.0,
|
2024-06-20 22:00:26 +00:00
|
|
|
rag_min_score_rerank: 0.0,
|
2024-06-05 01:02:23 +00:00
|
|
|
rag_template: None,
|
2024-06-23 14:09:47 +00:00
|
|
|
|
2024-07-27 06:52:13 +00:00
|
|
|
document_loaders: Default::default(),
|
2024-06-23 14:09:47 +00:00
|
|
|
|
|
|
|
highlight: true,
|
|
|
|
light_theme: false,
|
2024-04-23 06:32:06 +00:00
|
|
|
left_prompt: None,
|
|
|
|
right_prompt: None,
|
2024-06-23 14:09:47 +00:00
|
|
|
|
2024-04-23 06:32:06 +00:00
|
|
|
clients: vec![],
|
2024-06-23 14:09:47 +00:00
|
|
|
|
2023-03-11 03:26:24 +00:00
|
|
|
role: None,
|
2023-10-28 13:39:17 +00:00
|
|
|
session: None,
|
2024-06-05 01:02:23 +00:00
|
|
|
rag: None,
|
2024-06-21 22:54:03 +00:00
|
|
|
agent: None,
|
2023-11-02 22:52:57 +00:00
|
|
|
model: Default::default(),
|
2024-06-11 03:00:12 +00:00
|
|
|
functions: Default::default(),
|
2024-04-23 23:16:56 +00:00
|
|
|
working_mode: WorkingMode::Command,
|
2023-10-28 13:39:17 +00:00
|
|
|
last_message: None,
|
2023-03-11 03:26:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-02 02:45:11 +00:00
|
|
|
pub type GlobalConfig = Arc<RwLock<Config>>;
|
2023-03-05 14:51:29 +00:00
|
|
|
|
2023-03-02 11:52:11 +00:00
|
|
|
impl Config {
|
2024-07-30 13:38:25 +00:00
|
|
|
pub fn init(working_mode: WorkingMode) -> Result<Self> {
|
2023-03-11 03:26:24 +00:00
|
|
|
let config_path = Self::config_file()?;
|
2024-07-27 07:50:08 +00:00
|
|
|
let mut config = if !config_path.exists() {
|
2024-07-30 13:38:25 +00:00
|
|
|
match env::var(get_env_name("platform")) {
|
|
|
|
Ok(v) => Self::load_dynamic(&v)?,
|
|
|
|
Err(_) => {
|
2024-07-27 07:50:08 +00:00
|
|
|
if *IS_STDOUT_TERMINAL {
|
|
|
|
create_config_file(&config_path)?;
|
|
|
|
}
|
|
|
|
Self::load_from_file(&config_path)?
|
|
|
|
}
|
|
|
|
}
|
2023-03-11 03:26:24 +00:00
|
|
|
} else {
|
2024-07-27 07:50:08 +00:00
|
|
|
Self::load_from_file(&config_path)?
|
2023-03-11 03:26:24 +00:00
|
|
|
};
|
2023-10-26 08:42:54 +00:00
|
|
|
|
2024-07-27 06:52:13 +00:00
|
|
|
config.working_mode = working_mode;
|
|
|
|
|
|
|
|
config.load_envs();
|
|
|
|
|
2023-10-30 02:07:01 +00:00
|
|
|
if let Some(wrap) = config.wrap.clone() {
|
|
|
|
config.set_wrap(&wrap)?;
|
|
|
|
}
|
2023-10-28 13:39:17 +00:00
|
|
|
|
2024-07-02 11:06:18 +00:00
|
|
|
config.load_functions()?;
|
2023-11-01 02:28:54 +00:00
|
|
|
|
2023-11-02 22:52:57 +00:00
|
|
|
config.setup_model()?;
|
2024-06-27 22:24:20 +00:00
|
|
|
config.setup_document_loaders();
|
2023-03-09 15:30:12 +00:00
|
|
|
|
2023-03-02 11:52:11 +00:00
|
|
|
Ok(config)
|
|
|
|
}
|
2023-03-03 04:43:34 +00:00
|
|
|
|
2023-03-04 21:50:30 +00:00
|
|
|
pub fn config_dir() -> Result<PathBuf> {
|
2023-03-11 03:26:24 +00:00
|
|
|
let env_name = get_env_name("config_dir");
|
2023-07-10 03:07:09 +00:00
|
|
|
let path = if let Some(v) = env::var_os(env_name) {
|
|
|
|
PathBuf::from(v)
|
|
|
|
} else {
|
|
|
|
let mut dir = dirs::config_dir().ok_or_else(|| anyhow!("Not found config dir"))?;
|
|
|
|
dir.push(env!("CARGO_CRATE_NAME"));
|
|
|
|
dir
|
2023-03-03 01:38:53 +00:00
|
|
|
};
|
2023-03-04 21:50:30 +00:00
|
|
|
Ok(path)
|
|
|
|
}
|
|
|
|
|
2023-10-28 13:39:17 +00:00
|
|
|
pub fn local_path(name: &str) -> Result<PathBuf> {
|
2023-03-04 21:50:30 +00:00
|
|
|
let mut path = Self::config_dir()?;
|
2023-03-03 01:38:53 +00:00
|
|
|
path.push(name);
|
|
|
|
Ok(path)
|
|
|
|
}
|
2023-03-03 04:43:34 +00:00
|
|
|
|
|
|
|
pub fn config_file() -> Result<PathBuf> {
|
2024-06-05 07:18:34 +00:00
|
|
|
match env::var(get_env_name("config_file")) {
|
|
|
|
Ok(value) => Ok(PathBuf::from(value)),
|
|
|
|
Err(_) => Self::local_path(CONFIG_FILE_NAME),
|
|
|
|
}
|
2023-03-03 04:43:34 +00:00
|
|
|
}
|
|
|
|
|
2024-08-30 11:26:57 +00:00
|
|
|
pub fn roles_dir() -> Result<PathBuf> {
|
|
|
|
match env::var(get_env_name("roles_dir")) {
|
2024-06-05 07:18:34 +00:00
|
|
|
Ok(value) => Ok(PathBuf::from(value)),
|
2024-08-30 11:26:57 +00:00
|
|
|
Err(_) => Self::local_path(ROLES_DIR_NAME),
|
2024-06-05 07:18:34 +00:00
|
|
|
}
|
2023-03-03 04:43:34 +00:00
|
|
|
}
|
|
|
|
|
2024-08-30 11:26:57 +00:00
|
|
|
pub fn role_file(name: &str) -> Result<PathBuf> {
|
|
|
|
Ok(Self::roles_dir()?.join(format!("{name}.md")))
|
|
|
|
}
|
|
|
|
|
2024-07-04 06:40:52 +00:00
|
|
|
pub fn env_file() -> Result<PathBuf> {
|
|
|
|
match env::var(get_env_name("env_file")) {
|
|
|
|
Ok(value) => Ok(PathBuf::from(value)),
|
|
|
|
Err(_) => Self::local_path(ENV_FILE_NAME),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-11 03:00:12 +00:00
|
|
|
pub fn messages_file(&self) -> Result<PathBuf> {
|
2024-06-21 22:54:03 +00:00
|
|
|
match &self.agent {
|
2024-06-11 03:00:12 +00:00
|
|
|
None => match env::var(get_env_name("messages_file")) {
|
|
|
|
Ok(value) => Ok(PathBuf::from(value)),
|
|
|
|
Err(_) => Self::local_path(MESSAGES_FILE_NAME),
|
|
|
|
},
|
2024-06-21 22:54:03 +00:00
|
|
|
Some(agent) => Ok(Self::agent_config_dir(agent.name())?.join(MESSAGES_FILE_NAME)),
|
2024-06-05 07:18:34 +00:00
|
|
|
}
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
|
|
|
|
2024-06-11 03:00:12 +00:00
|
|
|
pub fn sessions_dir(&self) -> Result<PathBuf> {
|
2024-06-21 22:54:03 +00:00
|
|
|
match &self.agent {
|
2024-06-11 03:00:12 +00:00
|
|
|
None => match env::var(get_env_name("sessions_dir")) {
|
|
|
|
Ok(value) => Ok(PathBuf::from(value)),
|
|
|
|
Err(_) => Self::local_path(SESSIONS_DIR_NAME),
|
|
|
|
},
|
2024-06-21 22:54:03 +00:00
|
|
|
Some(agent) => Ok(Self::agent_config_dir(agent.name())?.join(SESSIONS_DIR_NAME)),
|
2024-06-05 07:18:34 +00:00
|
|
|
}
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
|
|
|
|
2024-06-05 01:02:23 +00:00
|
|
|
pub fn rags_dir() -> Result<PathBuf> {
|
2024-06-05 07:18:34 +00:00
|
|
|
match env::var(get_env_name("rags_dir")) {
|
|
|
|
Ok(value) => Ok(PathBuf::from(value)),
|
|
|
|
Err(_) => Self::local_path(RAGS_DIR_NAME),
|
|
|
|
}
|
2024-06-05 01:02:23 +00:00
|
|
|
}
|
|
|
|
|
2024-05-18 11:06:21 +00:00
|
|
|
pub fn functions_dir() -> Result<PathBuf> {
|
2024-06-05 07:18:34 +00:00
|
|
|
match env::var(get_env_name("functions_dir")) {
|
|
|
|
Ok(value) => Ok(PathBuf::from(value)),
|
|
|
|
Err(_) => Self::local_path(FUNCTIONS_DIR_NAME),
|
|
|
|
}
|
2024-05-18 11:06:21 +00:00
|
|
|
}
|
|
|
|
|
2024-06-11 03:00:12 +00:00
|
|
|
pub fn functions_file() -> Result<PathBuf> {
|
|
|
|
Ok(Self::functions_dir()?.join(FUNCTIONS_FILE_NAME))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn functions_bin_dir() -> Result<PathBuf> {
|
2024-06-12 00:56:56 +00:00
|
|
|
Ok(Self::functions_dir()?.join(FUNCTIONS_BIN_DIR_NAME))
|
2023-03-03 04:43:34 +00:00
|
|
|
}
|
|
|
|
|
2024-06-11 03:00:12 +00:00
|
|
|
pub fn session_file(&self, name: &str) -> Result<PathBuf> {
|
|
|
|
Ok(self.sessions_dir()?.join(format!("{name}.yaml")))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn rag_file(&self, name: &str) -> Result<PathBuf> {
|
2024-07-29 09:21:52 +00:00
|
|
|
let path = match &self.agent {
|
|
|
|
Some(agent) => Self::agent_rag_file(agent.name(), name)?,
|
|
|
|
None => Self::rags_dir()?.join(format!("{name}.bin")),
|
2024-06-11 03:00:12 +00:00
|
|
|
};
|
2024-06-05 01:02:23 +00:00
|
|
|
Ok(path)
|
|
|
|
}
|
|
|
|
|
2024-06-21 23:26:17 +00:00
|
|
|
pub fn agents_config_dir() -> Result<PathBuf> {
|
2024-07-24 12:33:52 +00:00
|
|
|
Self::local_path(AGENTS_DIR_NAME)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn agent_config_dir(name: &str) -> Result<PathBuf> {
|
2024-07-30 07:04:50 +00:00
|
|
|
match env::var(format!("{}_CONFIG_DIR", normalize_env_name(name))) {
|
2024-06-11 03:00:12 +00:00
|
|
|
Ok(value) => Ok(PathBuf::from(value)),
|
2024-07-24 12:33:52 +00:00
|
|
|
Err(_) => Ok(Self::agents_config_dir()?.join(name)),
|
2024-06-11 03:00:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-24 12:33:52 +00:00
|
|
|
pub fn agent_config_file(name: &str) -> Result<PathBuf> {
|
|
|
|
Ok(Self::agent_config_dir(name)?.join(CONFIG_FILE_NAME))
|
2024-06-11 03:00:12 +00:00
|
|
|
}
|
|
|
|
|
2024-07-29 09:21:52 +00:00
|
|
|
pub fn agent_rag_file(agent_name: &str, rag_name: &str) -> Result<PathBuf> {
|
|
|
|
Ok(Self::agent_config_dir(agent_name)?.join(format!("{rag_name}.bin")))
|
2024-06-11 03:00:12 +00:00
|
|
|
}
|
|
|
|
|
2024-07-09 13:22:51 +00:00
|
|
|
pub fn agent_variables_file(name: &str) -> Result<PathBuf> {
|
|
|
|
Ok(Self::agent_config_dir(name)?.join(AGENT_VARIABLES_FILE_NAME))
|
|
|
|
}
|
|
|
|
|
2024-06-21 23:26:17 +00:00
|
|
|
pub fn agents_functions_dir() -> Result<PathBuf> {
|
2024-07-24 12:33:52 +00:00
|
|
|
Ok(Self::functions_dir()?.join(AGENTS_DIR_NAME))
|
2024-06-21 23:26:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn agent_functions_dir(name: &str) -> Result<PathBuf> {
|
2024-07-30 07:04:50 +00:00
|
|
|
match env::var(format!("{}_FUNCTIONS_DIR", normalize_env_name(name))) {
|
2024-07-24 12:33:52 +00:00
|
|
|
Ok(value) => Ok(PathBuf::from(value)),
|
|
|
|
Err(_) => Ok(Self::agents_functions_dir()?.join(name)),
|
|
|
|
}
|
2024-06-11 03:00:12 +00:00
|
|
|
}
|
|
|
|
|
2024-06-02 11:27:41 +00:00
|
|
|
pub fn state(&self) -> StateFlags {
|
|
|
|
let mut flags = StateFlags::empty();
|
2023-11-27 07:39:55 +00:00
|
|
|
if let Some(session) = &self.session {
|
|
|
|
if session.is_empty() {
|
2024-06-02 11:27:41 +00:00
|
|
|
flags |= StateFlags::SESSION_EMPTY;
|
2023-11-27 07:39:55 +00:00
|
|
|
} else {
|
2024-06-02 11:27:41 +00:00
|
|
|
flags |= StateFlags::SESSION;
|
2023-11-27 07:39:55 +00:00
|
|
|
}
|
|
|
|
}
|
2024-06-21 22:54:03 +00:00
|
|
|
if self.agent.is_some() {
|
2024-06-23 15:09:47 +00:00
|
|
|
flags |= StateFlags::AGENT;
|
2024-06-11 03:00:12 +00:00
|
|
|
}
|
2024-06-02 11:27:41 +00:00
|
|
|
if self.role.is_some() {
|
2024-06-05 01:02:23 +00:00
|
|
|
flags |= StateFlags::ROLE;
|
|
|
|
}
|
|
|
|
if self.rag.is_some() {
|
|
|
|
flags |= StateFlags::RAG;
|
2024-06-02 11:27:41 +00:00
|
|
|
}
|
|
|
|
flags
|
2023-11-27 07:39:55 +00:00
|
|
|
}
|
|
|
|
|
2024-07-21 09:24:04 +00:00
|
|
|
pub fn log(is_serve: bool) -> Result<(LevelFilter, Option<PathBuf>)> {
|
|
|
|
let log_level = env::var(get_env_name("log_level"))
|
|
|
|
.ok()
|
|
|
|
.and_then(|v| v.parse().ok())
|
|
|
|
.unwrap_or(match cfg!(debug_assertions) {
|
|
|
|
true => LevelFilter::Debug,
|
|
|
|
false => {
|
|
|
|
if is_serve {
|
|
|
|
LevelFilter::Info
|
|
|
|
} else {
|
|
|
|
LevelFilter::Off
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if log_level == LevelFilter::Off {
|
|
|
|
return Ok((log_level, None));
|
|
|
|
}
|
|
|
|
let log_path = match env::var(get_env_name("log_path")) {
|
|
|
|
Ok(v) => Some(PathBuf::from(v)),
|
|
|
|
Err(_) => match is_serve {
|
|
|
|
true => None,
|
|
|
|
false => Some(Config::local_path(&format!(
|
|
|
|
"{}.log",
|
|
|
|
env!("CARGO_CRATE_NAME")
|
|
|
|
))?),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
Ok((log_level, log_path))
|
|
|
|
}
|
|
|
|
|
2024-06-11 03:00:12 +00:00
|
|
|
pub fn current_model(&self) -> &Model {
|
|
|
|
if let Some(session) = self.session.as_ref() {
|
|
|
|
session.model()
|
2024-06-21 22:54:03 +00:00
|
|
|
} else if let Some(agent) = self.agent.as_ref() {
|
|
|
|
agent.model()
|
2024-06-11 03:00:12 +00:00
|
|
|
} else if let Some(role) = self.role.as_ref() {
|
|
|
|
role.model()
|
|
|
|
} else {
|
|
|
|
&self.model
|
|
|
|
}
|
2024-06-05 21:27:21 +00:00
|
|
|
}
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
pub fn role_like_mut(&mut self) -> Option<&mut dyn RoleLike> {
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
|
|
|
Some(session)
|
2024-06-21 22:54:03 +00:00
|
|
|
} else if let Some(agent) = self.agent.as_mut() {
|
|
|
|
Some(agent)
|
2024-06-11 03:46:29 +00:00
|
|
|
} else if let Some(role) = self.role.as_mut() {
|
|
|
|
Some(role)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-11 03:00:12 +00:00
|
|
|
pub fn extract_role(&self) -> Role {
|
|
|
|
let mut role = if let Some(session) = self.session.as_ref() {
|
|
|
|
session.to_role()
|
2024-06-21 22:54:03 +00:00
|
|
|
} else if let Some(agent) = self.agent.as_ref() {
|
|
|
|
agent.to_role()
|
2024-06-11 03:00:12 +00:00
|
|
|
} else if let Some(role) = self.role.as_ref() {
|
|
|
|
role.clone()
|
2024-03-27 00:11:00 +00:00
|
|
|
} else {
|
2024-06-11 03:00:12 +00:00
|
|
|
let mut role = Role::default();
|
|
|
|
role.batch_set(&self.model, self.temperature, self.top_p, None);
|
|
|
|
role
|
|
|
|
};
|
|
|
|
if role.temperature().is_none() && self.temperature.is_some() {
|
|
|
|
role.set_temperature(self.temperature);
|
|
|
|
}
|
|
|
|
if role.top_p().is_none() && self.top_p.is_some() {
|
|
|
|
role.set_top_p(self.top_p);
|
2023-10-30 04:58:11 +00:00
|
|
|
}
|
2024-07-30 23:57:19 +00:00
|
|
|
if role.use_tools().is_none() && self.use_tools.is_some() {
|
2024-07-05 11:49:08 +00:00
|
|
|
role.set_use_tools(self.use_tools.clone())
|
|
|
|
}
|
2024-06-11 03:00:12 +00:00
|
|
|
role
|
2024-03-04 03:08:59 +00:00
|
|
|
}
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
pub fn info(&self) -> Result<String> {
|
2024-06-21 22:54:03 +00:00
|
|
|
if let Some(agent) = &self.agent {
|
|
|
|
let output = agent.export()?;
|
2024-06-13 11:41:54 +00:00
|
|
|
if let Some(session) = &self.session {
|
|
|
|
let session = session
|
|
|
|
.export()?
|
|
|
|
.split('\n')
|
|
|
|
.map(|v| format!(" {v}"))
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join("\n");
|
|
|
|
Ok(format!("{output}session:\n{session}"))
|
|
|
|
} else {
|
|
|
|
Ok(output)
|
|
|
|
}
|
|
|
|
} else if let Some(session) = &self.session {
|
2024-06-11 03:46:29 +00:00
|
|
|
session.export()
|
|
|
|
} else if let Some(role) = &self.role {
|
2024-08-30 11:26:57 +00:00
|
|
|
Ok(role.export())
|
2024-06-11 03:46:29 +00:00
|
|
|
} else if let Some(rag) = &self.rag {
|
|
|
|
rag.export()
|
2023-10-30 02:07:01 +00:00
|
|
|
} else {
|
2024-06-11 03:46:29 +00:00
|
|
|
self.sysinfo()
|
2023-03-16 09:02:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-11 03:00:12 +00:00
|
|
|
pub fn sysinfo(&self) -> Result<String> {
|
2023-12-22 22:35:23 +00:00
|
|
|
let display_path = |path: &Path| path.display().to_string();
|
2023-10-30 02:07:01 +00:00
|
|
|
let wrap = self
|
|
|
|
.wrap
|
|
|
|
.clone()
|
|
|
|
.map_or_else(|| String::from("no"), |v| v.to_string());
|
2024-06-11 03:00:12 +00:00
|
|
|
let role = self.extract_role();
|
2024-07-21 09:24:04 +00:00
|
|
|
let mut items = vec![
|
2024-06-11 03:00:12 +00:00
|
|
|
("model", role.model().id()),
|
2024-04-30 00:57:22 +00:00
|
|
|
(
|
|
|
|
"max_output_tokens",
|
|
|
|
self.model
|
2024-05-08 05:46:26 +00:00
|
|
|
.max_tokens_param()
|
2024-04-30 00:57:22 +00:00
|
|
|
.map(|v| format!("{v} (current model)"))
|
|
|
|
.unwrap_or_else(|| "-".into()),
|
|
|
|
),
|
2024-06-11 03:00:12 +00:00
|
|
|
("temperature", format_option_value(&role.temperature())),
|
|
|
|
("top_p", format_option_value(&role.top_p())),
|
2024-06-23 14:09:47 +00:00
|
|
|
("dry_run", self.dry_run.to_string()),
|
2024-07-28 00:56:00 +00:00
|
|
|
("stream", self.stream.to_string()),
|
2024-06-23 14:09:47 +00:00
|
|
|
("save", self.save.to_string()),
|
2024-07-27 06:52:13 +00:00
|
|
|
("keybindings", self.keybindings.clone()),
|
2024-06-23 14:09:47 +00:00
|
|
|
("wrap", wrap),
|
|
|
|
("wrap_code", self.wrap_code.to_string()),
|
|
|
|
("save_session", format_option_value(&self.save_session)),
|
|
|
|
("compress_threshold", self.compress_threshold.to_string()),
|
|
|
|
("function_calling", self.function_calling.to_string()),
|
2024-07-05 11:49:08 +00:00
|
|
|
("use_tools", format_option_value(&role.use_tools())),
|
2024-06-20 22:00:26 +00:00
|
|
|
(
|
2024-06-24 23:50:19 +00:00
|
|
|
"rag_reranker_model",
|
|
|
|
format_option_value(&self.rag_reranker_model),
|
2024-06-20 22:00:26 +00:00
|
|
|
),
|
2024-06-05 01:02:23 +00:00
|
|
|
("rag_top_k", self.rag_top_k.to_string()),
|
2023-03-05 14:51:29 +00:00
|
|
|
("highlight", self.highlight.to_string()),
|
2023-11-07 23:08:48 +00:00
|
|
|
("light_theme", self.light_theme.to_string()),
|
2023-11-07 15:07:42 +00:00
|
|
|
("config_file", display_path(&Self::config_file()?)),
|
2024-08-30 11:26:57 +00:00
|
|
|
("roles_dir", display_path(&Self::roles_dir()?)),
|
2024-07-04 06:40:52 +00:00
|
|
|
("env_file", display_path(&Self::env_file()?)),
|
2024-05-18 11:06:21 +00:00
|
|
|
("functions_dir", display_path(&Self::functions_dir()?)),
|
2024-06-11 03:00:12 +00:00
|
|
|
("rags_dir", display_path(&Self::rags_dir()?)),
|
|
|
|
("sessions_dir", display_path(&self.sessions_dir()?)),
|
|
|
|
("messages_file", display_path(&self.messages_file()?)),
|
2023-03-05 14:51:29 +00:00
|
|
|
];
|
2024-07-21 09:24:04 +00:00
|
|
|
if let Ok((_, Some(log_path))) = Self::log(self.working_mode.is_serve()) {
|
|
|
|
items.push(("log_path", display_path(&log_path)));
|
|
|
|
}
|
2023-11-02 01:53:54 +00:00
|
|
|
let output = items
|
|
|
|
.iter()
|
2024-08-30 11:26:57 +00:00
|
|
|
.map(|(name, value)| format!("{name:<24}{value}\n"))
|
2023-11-02 01:53:54 +00:00
|
|
|
.collect::<Vec<String>>()
|
2024-08-30 11:26:57 +00:00
|
|
|
.join("");
|
2023-03-05 14:51:29 +00:00
|
|
|
Ok(output)
|
|
|
|
}
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
pub fn update(&mut self, data: &str) -> Result<()> {
|
|
|
|
let parts: Vec<&str> = data.split_whitespace().collect();
|
|
|
|
if parts.len() != 2 {
|
|
|
|
bail!("Usage: .set <key> <value>. If value is null, unset key.");
|
2023-11-02 01:53:54 +00:00
|
|
|
}
|
2024-06-11 03:46:29 +00:00
|
|
|
let key = parts[0];
|
|
|
|
let value = parts[1];
|
|
|
|
match key {
|
|
|
|
"max_output_tokens" => {
|
|
|
|
let value = parse_value(value)?;
|
2024-06-17 07:17:07 +00:00
|
|
|
self.set_max_output_tokens(value);
|
2024-06-11 03:46:29 +00:00
|
|
|
}
|
|
|
|
"temperature" => {
|
|
|
|
let value = parse_value(value)?;
|
|
|
|
self.set_temperature(value);
|
|
|
|
}
|
|
|
|
"top_p" => {
|
|
|
|
let value = parse_value(value)?;
|
|
|
|
self.set_top_p(value);
|
|
|
|
}
|
2024-07-28 00:56:00 +00:00
|
|
|
"dry_run" => {
|
|
|
|
let value = value.parse().with_context(|| "Invalid value")?;
|
|
|
|
self.dry_run = value;
|
|
|
|
}
|
|
|
|
"stream" => {
|
|
|
|
let value = value.parse().with_context(|| "Invalid value")?;
|
|
|
|
self.stream = value;
|
|
|
|
}
|
|
|
|
"save" => {
|
|
|
|
let value = value.parse().with_context(|| "Invalid value")?;
|
|
|
|
self.save = value;
|
|
|
|
}
|
2024-06-24 23:50:19 +00:00
|
|
|
"rag_reranker_model" => {
|
|
|
|
self.rag_reranker_model = if value == "null" {
|
2024-06-20 22:00:26 +00:00
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(value.to_string())
|
|
|
|
}
|
|
|
|
}
|
2024-06-11 03:46:29 +00:00
|
|
|
"rag_top_k" => {
|
|
|
|
if let Some(value) = parse_value(value)? {
|
|
|
|
self.rag_top_k = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"function_calling" => {
|
|
|
|
let value = value.parse().with_context(|| "Invalid value")?;
|
2024-07-02 11:06:18 +00:00
|
|
|
if value && self.functions.is_empty() {
|
2024-07-03 02:15:47 +00:00
|
|
|
bail!("Function calling cannot be enabled because no functions are installed.")
|
2024-07-02 11:06:18 +00:00
|
|
|
}
|
2024-06-11 03:46:29 +00:00
|
|
|
self.function_calling = value;
|
|
|
|
}
|
2024-07-05 11:49:08 +00:00
|
|
|
"use_tools" => {
|
|
|
|
let value = parse_value(value)?;
|
|
|
|
self.set_use_tools(value);
|
|
|
|
}
|
2024-06-11 03:46:29 +00:00
|
|
|
"save_session" => {
|
|
|
|
let value = parse_value(value)?;
|
|
|
|
self.set_save_session(value);
|
|
|
|
}
|
2024-07-28 00:56:00 +00:00
|
|
|
"compress_threshold" => {
|
|
|
|
let value = parse_value(value)?;
|
|
|
|
self.set_compress_threshold(value);
|
|
|
|
}
|
2024-06-11 03:46:29 +00:00
|
|
|
"highlight" => {
|
|
|
|
let value = value.parse().with_context(|| "Invalid value")?;
|
|
|
|
self.highlight = value;
|
|
|
|
}
|
|
|
|
_ => bail!("Unknown key `{key}`"),
|
2023-11-02 01:53:54 +00:00
|
|
|
}
|
2024-06-11 03:46:29 +00:00
|
|
|
Ok(())
|
2023-11-02 01:53:54 +00:00
|
|
|
}
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
pub fn set_temperature(&mut self, value: Option<f64>) {
|
|
|
|
match self.role_like_mut() {
|
|
|
|
Some(role_like) => role_like.set_temperature(value),
|
|
|
|
None => self.temperature = value,
|
2024-06-05 01:02:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
pub fn set_top_p(&mut self, value: Option<f64>) {
|
|
|
|
match self.role_like_mut() {
|
|
|
|
Some(role_like) => role_like.set_top_p(value),
|
|
|
|
None => self.top_p = value,
|
2024-06-11 03:00:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-05 11:49:08 +00:00
|
|
|
pub fn set_use_tools(&mut self, value: Option<String>) {
|
|
|
|
match self.role_like_mut() {
|
|
|
|
Some(role_like) => role_like.set_use_tools(value),
|
|
|
|
None => self.use_tools = value,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
pub fn set_save_session(&mut self, value: Option<bool>) {
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
|
|
|
session.set_save_session(value);
|
2023-11-02 01:53:54 +00:00
|
|
|
} else {
|
2024-06-11 03:46:29 +00:00
|
|
|
self.save_session = value;
|
2023-11-02 01:53:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
pub fn set_compress_threshold(&mut self, value: Option<usize>) {
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
|
|
|
session.set_compress_threshold(value);
|
|
|
|
} else {
|
|
|
|
self.compress_threshold = value.unwrap_or_default();
|
|
|
|
}
|
2023-11-02 01:53:54 +00:00
|
|
|
}
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
pub fn set_wrap(&mut self, value: &str) -> Result<()> {
|
|
|
|
if value == "no" {
|
|
|
|
self.wrap = None;
|
|
|
|
} else if value == "auto" {
|
|
|
|
self.wrap = Some(value.into());
|
2023-11-07 14:18:59 +00:00
|
|
|
} else {
|
2024-06-11 03:46:29 +00:00
|
|
|
value
|
|
|
|
.parse::<u16>()
|
|
|
|
.map_err(|_| anyhow!("Invalid wrap value"))?;
|
|
|
|
self.wrap = Some(value.into())
|
|
|
|
}
|
|
|
|
Ok(())
|
2023-03-05 22:45:00 +00:00
|
|
|
}
|
|
|
|
|
2024-06-17 07:17:07 +00:00
|
|
|
pub fn set_max_output_tokens(&mut self, value: Option<isize>) {
|
|
|
|
match self.role_like_mut() {
|
|
|
|
Some(role_like) => role_like.model_mut().set_max_tokens(value, true),
|
|
|
|
None => self.model.set_max_tokens(value, true),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
pub fn set_model(&mut self, model_id: &str) -> Result<()> {
|
2024-06-14 11:12:18 +00:00
|
|
|
let model = Model::retrieve_chat(self, model_id)?;
|
2024-06-11 03:46:29 +00:00
|
|
|
match self.role_like_mut() {
|
|
|
|
Some(role_like) => role_like.set_model(&model),
|
|
|
|
None => {
|
|
|
|
self.model = model;
|
2023-03-05 14:51:29 +00:00
|
|
|
}
|
2024-06-11 03:46:29 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn use_prompt(&mut self, prompt: &str) -> Result<()> {
|
2024-06-17 11:54:24 +00:00
|
|
|
let mut role = Role::new(TEMP_ROLE_NAME, prompt);
|
|
|
|
role.set_model(&self.model);
|
2024-06-11 03:46:29 +00:00
|
|
|
self.use_role_obj(role)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn use_role(&mut self, name: &str) -> Result<()> {
|
|
|
|
let role = self.retrieve_role(name)?;
|
|
|
|
self.use_role_obj(role)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn use_role_obj(&mut self, role: Role) -> Result<()> {
|
2024-06-21 22:54:03 +00:00
|
|
|
if self.agent.is_some() {
|
|
|
|
bail!("Cannot perform this action because you are using a agent")
|
2024-06-11 03:46:29 +00:00
|
|
|
}
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
|
|
|
session.guard_empty()?;
|
|
|
|
session.set_role(role);
|
|
|
|
} else {
|
|
|
|
self.role = Some(role);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn role_info(&self) -> Result<String> {
|
|
|
|
if let Some(role) = &self.role {
|
2024-08-30 11:26:57 +00:00
|
|
|
Ok(role.export())
|
2024-06-11 03:46:29 +00:00
|
|
|
} else {
|
|
|
|
bail!("No role")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn exit_role(&mut self) -> Result<()> {
|
|
|
|
if self.role.is_some() {
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
|
|
|
session.clear_role();
|
2023-11-02 08:08:11 +00:00
|
|
|
}
|
2024-06-11 03:46:29 +00:00
|
|
|
self.role = None;
|
2023-03-05 14:51:29 +00:00
|
|
|
}
|
2023-03-09 02:39:28 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
pub fn retrieve_role(&self, name: &str) -> Result<Role> {
|
2024-08-30 11:26:57 +00:00
|
|
|
let mut role = if Self::list_roles(false).contains(&name.to_string()) {
|
|
|
|
let path = Self::role_file(name)?;
|
|
|
|
let content = read_to_string(path)?;
|
|
|
|
Role::new(name, &content)
|
|
|
|
} else {
|
|
|
|
BUILTIN_ROLES
|
|
|
|
.iter()
|
|
|
|
.find(|v| v.name() == name)
|
|
|
|
.cloned()
|
|
|
|
.ok_or_else(|| anyhow!("Unknown role `{name}`"))?
|
|
|
|
};
|
2024-06-11 03:46:29 +00:00
|
|
|
match role.model_id() {
|
|
|
|
Some(model_id) => {
|
|
|
|
if self.model.id() != model_id {
|
2024-06-14 11:12:18 +00:00
|
|
|
let model = Model::retrieve_chat(self, model_id)?;
|
2024-06-11 03:46:29 +00:00
|
|
|
role.set_model(&model);
|
2024-08-13 01:49:12 +00:00
|
|
|
} else {
|
|
|
|
role.set_model(&self.model);
|
2024-06-11 03:00:12 +00:00
|
|
|
}
|
|
|
|
}
|
2024-06-11 03:46:29 +00:00
|
|
|
None => role.set_model(&self.model),
|
|
|
|
}
|
|
|
|
Ok(role)
|
2024-06-11 03:00:12 +00:00
|
|
|
}
|
|
|
|
|
2024-08-30 11:26:57 +00:00
|
|
|
pub fn new_role(&mut self, name: &str) -> Result<()> {
|
|
|
|
let ans = Confirm::new("Create a new role?")
|
|
|
|
.with_default(true)
|
|
|
|
.prompt()?;
|
|
|
|
if ans {
|
|
|
|
self.upsert_role(name)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn edit_role(&mut self) -> Result<()> {
|
|
|
|
if let Some(name) = self.role.as_ref().map(|v| v.name().to_string()) {
|
|
|
|
self.upsert_role(&name)
|
|
|
|
} else {
|
|
|
|
bail!("No role")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn upsert_role(&mut self, name: &str) -> Result<()> {
|
|
|
|
let role_path = Self::role_file(name)?;
|
|
|
|
ensure_parent_exists(&role_path)?;
|
|
|
|
let editor = self.editor()?;
|
|
|
|
edit_file(&editor, &role_path)?;
|
|
|
|
self.use_role(name)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn save_role(&mut self, name: Option<&str>) -> Result<()> {
|
|
|
|
let mut role_name = match &self.role {
|
|
|
|
Some(role) => match name {
|
|
|
|
Some(v) => v.to_string(),
|
|
|
|
None => role.name().to_string(),
|
|
|
|
},
|
|
|
|
None => bail!("No role"),
|
|
|
|
};
|
|
|
|
if role_name == TEMP_ROLE_NAME {
|
|
|
|
role_name = Text::new("Role name:")
|
|
|
|
.with_validator(|input: &str| {
|
|
|
|
if input.trim().is_empty() {
|
|
|
|
Ok(Validation::Invalid("This field is required".into()))
|
|
|
|
} else {
|
|
|
|
Ok(Validation::Valid)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.prompt()?;
|
|
|
|
}
|
|
|
|
let role_path = Self::role_file(&role_name)?;
|
|
|
|
if let Some(role) = self.role.as_mut() {
|
|
|
|
let old_name = role.name().to_string();
|
|
|
|
role.save(&role_name, &role_path, self.working_mode.is_repl())?;
|
|
|
|
if old_name != role_name {
|
|
|
|
if let Ok(path) = Self::role_file(&old_name) {
|
|
|
|
let _ = remove_file(&path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn all_roles() -> Vec<Role> {
|
|
|
|
let mut roles: HashMap<String, Role> = BUILTIN_ROLES
|
|
|
|
.iter()
|
|
|
|
.map(|v| (v.name().to_string(), v.clone()))
|
|
|
|
.collect();
|
|
|
|
let names = Self::list_roles(false);
|
|
|
|
for name in names {
|
|
|
|
if let Ok(path) = Self::role_file(&name) {
|
|
|
|
if let Ok(content) = read_to_string(&path) {
|
|
|
|
let role = Role::new(&name, &content);
|
|
|
|
roles.insert(name, role);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let mut roles: Vec<_> = roles.into_values().collect();
|
|
|
|
roles.sort_unstable_by(|a, b| a.name().cmp(b.name()));
|
|
|
|
roles
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn list_roles(with_builtin: bool) -> Vec<String> {
|
|
|
|
let mut names = HashSet::new();
|
|
|
|
if let Some(rd) = Self::roles_dir().ok().and_then(|dir| read_dir(dir).ok()) {
|
|
|
|
for entry in rd.flatten() {
|
|
|
|
if let Some(name) = entry
|
|
|
|
.file_name()
|
|
|
|
.to_str()
|
|
|
|
.and_then(|v| v.strip_suffix(".md"))
|
|
|
|
{
|
|
|
|
names.insert(name.to_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if with_builtin {
|
|
|
|
names.extend(BUILTIN_ROLES.iter().map(|v| v.name().to_string()));
|
|
|
|
}
|
|
|
|
let mut names: Vec<_> = names.into_iter().collect();
|
|
|
|
names.sort_unstable();
|
|
|
|
names
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn has_role(name: &str) -> bool {
|
|
|
|
Self::list_roles(true).iter().any(|v| v == name)
|
|
|
|
}
|
|
|
|
|
2024-06-12 01:21:43 +00:00
|
|
|
pub fn use_session(&mut self, session_name: Option<&str>) -> Result<()> {
|
2023-10-28 13:39:17 +00:00
|
|
|
if self.session.is_some() {
|
2023-12-13 00:11:10 +00:00
|
|
|
bail!(
|
|
|
|
"Already in a session, please run '.exit session' first to exit the current session."
|
|
|
|
);
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
2024-06-12 01:21:43 +00:00
|
|
|
let mut session;
|
|
|
|
match session_name {
|
2024-06-12 01:41:05 +00:00
|
|
|
None | Some(TEMP_SESSION_NAME) => {
|
2024-06-11 03:00:12 +00:00
|
|
|
let session_file = self.session_file(TEMP_SESSION_NAME)?;
|
2023-10-28 13:39:17 +00:00
|
|
|
if session_file.exists() {
|
2024-03-26 23:00:28 +00:00
|
|
|
remove_file(session_file).with_context(|| {
|
|
|
|
format!("Failed to cleanup previous '{TEMP_SESSION_NAME}' session")
|
|
|
|
})?;
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
2024-06-12 01:21:43 +00:00
|
|
|
session = Some(Session::new(self, TEMP_SESSION_NAME));
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
|
|
|
Some(name) => {
|
2024-06-11 03:00:12 +00:00
|
|
|
let session_path = self.session_file(name)?;
|
2023-10-28 13:39:17 +00:00
|
|
|
if !session_path.exists() {
|
2024-06-12 01:21:43 +00:00
|
|
|
session = Some(Session::new(self, name));
|
2023-10-28 13:39:17 +00:00
|
|
|
} else {
|
2024-06-12 01:21:43 +00:00
|
|
|
session = Some(Session::load(self, name, &session_path)?);
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-12 01:21:43 +00:00
|
|
|
if let Some(session) = session.as_mut() {
|
2023-10-28 13:39:17 +00:00
|
|
|
if session.is_empty() {
|
2024-06-17 05:19:00 +00:00
|
|
|
if let Some((input, output)) = &self.last_message {
|
2024-06-21 22:54:03 +00:00
|
|
|
if self.agent.is_some() == input.with_agent() {
|
2024-06-12 01:21:43 +00:00
|
|
|
let ans = Confirm::new(
|
|
|
|
"Start a session that incorporates the last question and answer?",
|
|
|
|
)
|
|
|
|
.with_default(false)
|
|
|
|
.prompt()?;
|
|
|
|
if ans {
|
|
|
|
session.add_message(input, output)?;
|
|
|
|
}
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
|
|
|
}
|
2023-03-09 02:39:28 +00:00
|
|
|
}
|
|
|
|
}
|
2024-06-12 01:21:43 +00:00
|
|
|
self.session = session;
|
2023-03-09 02:39:28 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
pub fn session_info(&self) -> Result<String> {
|
|
|
|
if let Some(session) = &self.session {
|
|
|
|
let render_options = self.render_options()?;
|
|
|
|
let mut markdown_render = MarkdownRender::init(render_options)?;
|
|
|
|
session.render(&mut markdown_render)
|
|
|
|
} else {
|
|
|
|
bail!("No session")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-05 01:02:23 +00:00
|
|
|
pub fn exit_session(&mut self) -> Result<()> {
|
2023-10-28 13:39:17 +00:00
|
|
|
if let Some(mut session) = self.session.take() {
|
2024-06-11 03:00:12 +00:00
|
|
|
let sessions_dir = self.sessions_dir()?;
|
2024-06-17 11:54:24 +00:00
|
|
|
session.exit(&sessions_dir, self.working_mode.is_repl())?;
|
2023-10-28 13:39:17 +00:00
|
|
|
self.last_message = None;
|
2024-03-27 03:20:00 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-06-17 02:59:59 +00:00
|
|
|
pub fn save_session(&mut self, name: Option<&str>) -> Result<()> {
|
2024-08-30 11:26:57 +00:00
|
|
|
let session_name = match &self.session {
|
2024-06-17 02:59:59 +00:00
|
|
|
Some(session) => match name {
|
|
|
|
Some(v) => v.to_string(),
|
|
|
|
None => session.name().to_string(),
|
|
|
|
},
|
|
|
|
None => bail!("No session"),
|
|
|
|
};
|
2024-08-30 11:26:57 +00:00
|
|
|
let session_path = self.session_file(&session_name)?;
|
2024-03-27 03:20:00 +00:00
|
|
|
if let Some(session) = self.session.as_mut() {
|
2024-08-30 11:26:57 +00:00
|
|
|
let old_name = session.name().to_string();
|
|
|
|
session.save(&session_name, &session_path, self.working_mode.is_repl())?;
|
|
|
|
if old_name != session_name {
|
|
|
|
if let Ok(path) = self.session_file(&old_name) {
|
|
|
|
let _ = remove_file(&path);
|
|
|
|
}
|
|
|
|
}
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
2023-03-09 02:39:28 +00:00
|
|
|
}
|
|
|
|
|
2024-06-17 02:59:59 +00:00
|
|
|
pub fn edit_session(&mut self) -> Result<()> {
|
|
|
|
let name = match &self.session {
|
|
|
|
Some(session) => session.name().to_string(),
|
|
|
|
None => bail!("No session"),
|
|
|
|
};
|
|
|
|
let session_path = self.session_file(&name)?;
|
|
|
|
self.save_session(Some(&name))?;
|
2024-08-30 11:26:57 +00:00
|
|
|
let editor = self.editor()?;
|
2024-06-17 02:59:59 +00:00
|
|
|
edit_file(&editor, &session_path).with_context(|| {
|
|
|
|
format!(
|
|
|
|
"Failed to edit '{}' with '{editor}'",
|
|
|
|
session_path.display()
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
self.session = Some(Session::load(self, &name, &session_path)?);
|
2024-06-17 05:19:00 +00:00
|
|
|
self.last_message = None;
|
2024-06-17 02:59:59 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-03-03 07:25:21 +00:00
|
|
|
pub fn clear_session_messages(&mut self) -> Result<()> {
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
2024-03-04 03:08:59 +00:00
|
|
|
session.clear_messages();
|
2024-07-29 22:20:30 +00:00
|
|
|
if let Some(prompt) = self.agent.as_ref().map(|v| v.interpolated_instructions()) {
|
|
|
|
session.update_role_prompt(&prompt);
|
|
|
|
}
|
2024-06-17 05:19:00 +00:00
|
|
|
} else {
|
|
|
|
bail!("No session")
|
2024-03-03 07:25:21 +00:00
|
|
|
}
|
2024-06-17 05:19:00 +00:00
|
|
|
self.last_message = None;
|
2024-03-03 07:25:21 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-11-02 07:33:22 +00:00
|
|
|
pub fn list_sessions(&self) -> Vec<String> {
|
2024-06-11 03:00:12 +00:00
|
|
|
let sessions_dir = match self.sessions_dir() {
|
2023-11-02 07:33:22 +00:00
|
|
|
Ok(dir) => dir,
|
|
|
|
Err(_) => return vec![],
|
|
|
|
};
|
2023-11-27 06:04:50 +00:00
|
|
|
match read_dir(sessions_dir) {
|
2023-10-28 13:39:17 +00:00
|
|
|
Ok(rd) => {
|
|
|
|
let mut names = vec![];
|
2023-11-02 07:33:22 +00:00
|
|
|
for entry in rd.flatten() {
|
2023-10-28 13:39:17 +00:00
|
|
|
let name = entry.file_name();
|
|
|
|
if let Some(name) = name.to_string_lossy().strip_suffix(".yaml") {
|
|
|
|
names.push(name.to_string());
|
|
|
|
}
|
|
|
|
}
|
2023-11-27 09:22:16 +00:00
|
|
|
names.sort_unstable();
|
2023-11-02 07:33:22 +00:00
|
|
|
names
|
2023-10-28 13:39:17 +00:00
|
|
|
}
|
2023-11-02 07:33:22 +00:00
|
|
|
Err(_) => vec![],
|
2023-03-09 02:39:28 +00:00
|
|
|
}
|
2023-03-05 14:51:29 +00:00
|
|
|
}
|
|
|
|
|
2024-03-04 03:08:59 +00:00
|
|
|
pub fn should_compress_session(&mut self) -> bool {
|
2024-03-06 01:46:47 +00:00
|
|
|
if let Some(session) = self.session.as_mut() {
|
|
|
|
if session.need_compress(self.compress_threshold) {
|
2024-06-11 03:00:12 +00:00
|
|
|
session.set_compressing(true);
|
2024-03-04 03:08:59 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn compress_session(&mut self, summary: &str) {
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
2024-04-23 06:32:06 +00:00
|
|
|
let summary_prompt = self.summary_prompt.as_deref().unwrap_or(SUMMARY_PROMPT);
|
|
|
|
session.compress(format!("{}{}", summary_prompt, summary));
|
2024-03-04 03:08:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 06:32:06 +00:00
|
|
|
pub fn summarize_prompt(&self) -> &str {
|
|
|
|
self.summarize_prompt.as_deref().unwrap_or(SUMMARIZE_PROMPT)
|
|
|
|
}
|
|
|
|
|
2024-03-04 03:08:59 +00:00
|
|
|
pub fn is_compressing_session(&self) -> bool {
|
|
|
|
self.session
|
|
|
|
.as_ref()
|
2024-06-11 03:00:12 +00:00
|
|
|
.map(|v| v.compressing())
|
2024-03-04 03:08:59 +00:00
|
|
|
.unwrap_or_default()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn end_compressing_session(&mut self) {
|
|
|
|
if let Some(session) = self.session.as_mut() {
|
2024-06-11 03:00:12 +00:00
|
|
|
session.set_compressing(false);
|
2024-03-04 03:08:59 +00:00
|
|
|
}
|
2024-06-17 01:14:54 +00:00
|
|
|
self.last_message = None;
|
2024-03-04 03:08:59 +00:00
|
|
|
}
|
|
|
|
|
2024-06-05 01:02:23 +00:00
|
|
|
pub async fn use_rag(
|
|
|
|
config: &GlobalConfig,
|
|
|
|
rag: Option<&str>,
|
|
|
|
abort_signal: AbortSignal,
|
|
|
|
) -> Result<()> {
|
2024-06-21 22:54:03 +00:00
|
|
|
if config.read().agent.is_some() {
|
|
|
|
bail!("Cannot perform this action because you are using a agent")
|
2024-06-05 01:02:23 +00:00
|
|
|
}
|
|
|
|
let rag = match rag {
|
|
|
|
None => {
|
2024-06-11 03:00:12 +00:00
|
|
|
let rag_path = config.read().rag_file(TEMP_RAG_NAME)?;
|
2024-06-05 01:02:23 +00:00
|
|
|
if rag_path.exists() {
|
|
|
|
remove_file(&rag_path).with_context(|| {
|
|
|
|
format!("Failed to cleanup previous '{TEMP_RAG_NAME}' rag")
|
|
|
|
})?;
|
|
|
|
}
|
2024-06-11 03:00:12 +00:00
|
|
|
Rag::init(config, TEMP_RAG_NAME, &rag_path, &[], abort_signal).await?
|
2024-06-05 01:02:23 +00:00
|
|
|
}
|
|
|
|
Some(name) => {
|
2024-06-11 03:00:12 +00:00
|
|
|
let rag_path = config.read().rag_file(name)?;
|
2024-06-05 01:02:23 +00:00
|
|
|
if !rag_path.exists() {
|
2024-06-11 03:00:12 +00:00
|
|
|
Rag::init(config, name, &rag_path, &[], abort_signal).await?
|
2024-06-05 01:02:23 +00:00
|
|
|
} else {
|
|
|
|
Rag::load(config, name, &rag_path)?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
config.write().rag = Some(Arc::new(rag));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-07-01 12:19:41 +00:00
|
|
|
pub async fn rebuild_rag(config: &GlobalConfig, abort_signal: AbortSignal) -> Result<()> {
|
|
|
|
let rag_name = match config.read().rag.clone() {
|
|
|
|
Some(v) => v.name().to_string(),
|
|
|
|
None => bail!("No RAG"),
|
|
|
|
};
|
|
|
|
let rag_path = config.read().rag_file(&rag_name)?;
|
|
|
|
let mut rag = Rag::load(config, &rag_name, &rag_path)?;
|
|
|
|
rag.rebuild(config, &rag_path, abort_signal).await?;
|
|
|
|
config.write().rag = Some(Arc::new(rag));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
pub fn rag_info(&self) -> Result<String> {
|
|
|
|
if let Some(rag) = &self.rag {
|
|
|
|
rag.export()
|
|
|
|
} else {
|
2024-07-01 12:19:41 +00:00
|
|
|
bail!("No RAG")
|
2024-06-11 03:46:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-05 01:02:23 +00:00
|
|
|
pub fn exit_rag(&mut self) -> Result<()> {
|
|
|
|
self.rag.take();
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn list_rags(&self) -> Vec<String> {
|
|
|
|
let rags_dir = match Self::rags_dir() {
|
|
|
|
Ok(dir) => dir,
|
|
|
|
Err(_) => return vec![],
|
|
|
|
};
|
|
|
|
match read_dir(rags_dir) {
|
|
|
|
Ok(rd) => {
|
|
|
|
let mut names = vec![];
|
|
|
|
for entry in rd.flatten() {
|
|
|
|
let name = entry.file_name();
|
|
|
|
if let Some(name) = name.to_string_lossy().strip_suffix(".bin") {
|
|
|
|
names.push(name.to_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
names.sort_unstable();
|
|
|
|
names
|
|
|
|
}
|
|
|
|
Err(_) => vec![],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn rag_template(&self, embeddings: &str, text: &str) -> String {
|
|
|
|
if embeddings.is_empty() {
|
|
|
|
return text.to_string();
|
|
|
|
}
|
|
|
|
self.rag_template
|
|
|
|
.as_deref()
|
|
|
|
.unwrap_or(RAG_TEMPLATE)
|
|
|
|
.replace("__CONTEXT__", embeddings)
|
|
|
|
.replace("__INPUT__", text)
|
|
|
|
}
|
|
|
|
|
2024-06-21 22:54:03 +00:00
|
|
|
pub async fn use_agent(
|
2024-06-11 03:00:12 +00:00
|
|
|
config: &GlobalConfig,
|
|
|
|
name: &str,
|
2024-06-13 11:41:54 +00:00
|
|
|
session: Option<&str>,
|
2024-06-11 03:00:12 +00:00
|
|
|
abort_signal: AbortSignal,
|
|
|
|
) -> Result<()> {
|
|
|
|
if !config.read().function_calling {
|
2024-07-02 11:06:18 +00:00
|
|
|
bail!("Please enable function calling before using the agent.");
|
2024-06-11 03:00:12 +00:00
|
|
|
}
|
2024-06-21 22:54:03 +00:00
|
|
|
if config.read().agent.is_some() {
|
|
|
|
bail!("Already in a agent, please run '.exit agent' first to exit the current agent.");
|
2024-06-11 03:00:12 +00:00
|
|
|
}
|
2024-06-21 22:54:03 +00:00
|
|
|
let agent = Agent::init(config, name, abort_signal).await?;
|
2024-07-30 23:57:19 +00:00
|
|
|
let session = session.map(|v| v.to_string()).or_else(|| {
|
|
|
|
agent
|
|
|
|
.agent_prelude()
|
|
|
|
.map(|v| v.to_string())
|
|
|
|
.or_else(|| config.read().agent_prelude.clone())
|
|
|
|
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
|
|
|
});
|
2024-06-21 22:54:03 +00:00
|
|
|
config.write().rag = agent.rag();
|
|
|
|
config.write().agent = Some(agent);
|
2024-06-13 11:41:54 +00:00
|
|
|
if let Some(session) = session {
|
2024-06-11 07:44:35 +00:00
|
|
|
config.write().use_session(Some(&session))?;
|
|
|
|
}
|
2024-06-11 03:00:12 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-06-21 22:54:03 +00:00
|
|
|
pub fn agent_info(&self) -> Result<String> {
|
|
|
|
if let Some(agent) = &self.agent {
|
|
|
|
agent.export()
|
2024-06-11 03:46:29 +00:00
|
|
|
} else {
|
2024-06-21 22:54:03 +00:00
|
|
|
bail!("No agent")
|
2024-06-12 23:41:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-21 22:54:03 +00:00
|
|
|
pub fn agent_banner(&self) -> Result<String> {
|
|
|
|
if let Some(agent) = &self.agent {
|
|
|
|
Ok(agent.banner())
|
2024-06-12 23:41:29 +00:00
|
|
|
} else {
|
2024-06-21 22:54:03 +00:00
|
|
|
bail!("No agent")
|
2024-06-11 03:46:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-09 13:22:51 +00:00
|
|
|
pub fn set_agent_variable(&mut self, data: &str) -> Result<()> {
|
|
|
|
let parts: Vec<&str> = data.split_whitespace().collect();
|
|
|
|
if parts.len() != 2 {
|
|
|
|
bail!("Usage: .variable <key> <value>");
|
|
|
|
}
|
|
|
|
let key = parts[0];
|
|
|
|
let value = parts[1];
|
|
|
|
match self.agent.as_mut() {
|
|
|
|
Some(agent) => agent.set_variable(key, value)?,
|
|
|
|
None => bail!("No agent"),
|
|
|
|
};
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-06-21 22:54:03 +00:00
|
|
|
pub fn exit_agent(&mut self) -> Result<()> {
|
2024-06-17 11:54:24 +00:00
|
|
|
self.exit_session()?;
|
2024-06-21 22:54:03 +00:00
|
|
|
if self.agent.take().is_some() {
|
2024-06-17 09:03:28 +00:00
|
|
|
self.rag.take();
|
|
|
|
self.last_message = None;
|
|
|
|
}
|
2024-06-11 03:00:12 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
pub fn apply_prelude(&mut self) -> Result<()> {
|
2024-06-11 07:44:35 +00:00
|
|
|
let prelude = match self.working_mode {
|
|
|
|
WorkingMode::Command => self.prelude.as_ref(),
|
|
|
|
WorkingMode::Repl => self.repl_prelude.as_ref().or(self.prelude.as_ref()),
|
|
|
|
WorkingMode::Serve => return Ok(()),
|
|
|
|
};
|
|
|
|
let prelude = match prelude {
|
2024-07-30 23:57:19 +00:00
|
|
|
Some(v) => {
|
|
|
|
if v.is_empty() {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
v.to_string()
|
|
|
|
}
|
2024-06-11 07:44:35 +00:00
|
|
|
None => return Ok(()),
|
|
|
|
};
|
|
|
|
|
2024-06-11 03:46:29 +00:00
|
|
|
let err_msg = || format!("Invalid prelude '{}", prelude);
|
|
|
|
match prelude.split_once(':') {
|
|
|
|
Some(("role", name)) => {
|
2024-06-11 07:44:35 +00:00
|
|
|
if self.state().is_empty() {
|
2024-06-11 03:46:29 +00:00
|
|
|
self.use_role(name).with_context(err_msg)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Some(("session", name)) => {
|
|
|
|
if self.session.is_none() {
|
|
|
|
self.use_session(Some(name)).with_context(err_msg)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
bail!("{}", err_msg())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-08-16 10:44:13 +00:00
|
|
|
pub fn select_functions(&self, role: &Role) -> Option<Vec<FunctionDeclaration>> {
|
2024-07-05 11:49:08 +00:00
|
|
|
let mut functions = vec![];
|
2024-06-11 03:46:29 +00:00
|
|
|
if self.function_calling {
|
2024-07-09 01:36:50 +00:00
|
|
|
if let Some(use_tools) = role.use_tools() {
|
2024-07-05 11:49:08 +00:00
|
|
|
let mut tool_names: HashSet<String> = Default::default();
|
2024-07-09 01:36:50 +00:00
|
|
|
let declaration_names: HashSet<String> = self
|
|
|
|
.functions
|
|
|
|
.declarations()
|
|
|
|
.iter()
|
|
|
|
.map(|v| v.name.to_string())
|
|
|
|
.collect();
|
2024-07-05 11:49:08 +00:00
|
|
|
for item in use_tools.split(',') {
|
|
|
|
let item = item.trim();
|
|
|
|
if item == "all" {
|
|
|
|
tool_names.extend(declaration_names);
|
|
|
|
break;
|
|
|
|
} else if let Some(values) = self.mapping_tools.get(item) {
|
|
|
|
tool_names.extend(
|
|
|
|
values
|
|
|
|
.split(',')
|
|
|
|
.map(|v| v.to_string())
|
|
|
|
.filter(|v| declaration_names.contains(v)),
|
|
|
|
)
|
|
|
|
} else if declaration_names.contains(item) {
|
|
|
|
tool_names.insert(item.to_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
functions = self
|
|
|
|
.functions
|
|
|
|
.declarations()
|
|
|
|
.iter()
|
|
|
|
.filter_map(|v| {
|
|
|
|
if tool_names.contains(&v.name) {
|
|
|
|
Some(v.clone())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect();
|
2024-07-09 01:36:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(agent) = &self.agent {
|
|
|
|
let mut agent_functions = agent.functions().declarations().to_vec();
|
|
|
|
let tool_names: HashSet<String> = agent_functions
|
|
|
|
.iter()
|
|
|
|
.filter_map(|v| {
|
|
|
|
if v.agent {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(v.name.to_string())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
agent_functions.extend(
|
|
|
|
functions
|
|
|
|
.into_iter()
|
|
|
|
.filter(|v| !tool_names.contains(&v.name)),
|
|
|
|
);
|
|
|
|
functions = agent_functions;
|
|
|
|
}
|
2024-06-11 03:46:29 +00:00
|
|
|
};
|
2024-07-05 11:49:08 +00:00
|
|
|
if functions.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(functions)
|
|
|
|
}
|
2024-06-11 03:46:29 +00:00
|
|
|
}
|
|
|
|
|
2024-08-01 07:01:34 +00:00
|
|
|
pub fn editor(&self) -> Result<String> {
|
|
|
|
self.editor
|
2024-06-11 03:46:29 +00:00
|
|
|
.clone()
|
|
|
|
.or_else(|| env::var("VISUAL").ok().or_else(|| env::var("EDITOR").ok()))
|
2024-08-01 07:01:34 +00:00
|
|
|
.ok_or_else(|| anyhow!("No editor, please configure `editor` or set $EDITOR/$VISUAL environment variable."))
|
2024-06-11 03:46:29 +00:00
|
|
|
}
|
|
|
|
|
2024-07-12 12:25:06 +00:00
|
|
|
pub fn repl_complete(
|
|
|
|
&self,
|
|
|
|
cmd: &str,
|
|
|
|
args: &[&str],
|
|
|
|
line: &str,
|
|
|
|
) -> Vec<(String, Option<String>)> {
|
|
|
|
let mut values: Vec<(String, Option<String>)> = vec![];
|
|
|
|
let mut filter = "";
|
|
|
|
if args.len() == 1 {
|
|
|
|
values = match cmd {
|
2024-08-30 11:26:57 +00:00
|
|
|
".role" => Self::list_roles(true)
|
|
|
|
.into_iter()
|
|
|
|
.map(|v| (v, None))
|
2024-06-11 03:46:29 +00:00
|
|
|
.collect(),
|
|
|
|
".model" => list_chat_models(self)
|
|
|
|
.into_iter()
|
|
|
|
.map(|v| (v.id(), Some(v.description())))
|
|
|
|
.collect(),
|
|
|
|
".session" => self
|
|
|
|
.list_sessions()
|
|
|
|
.into_iter()
|
|
|
|
.map(|v| (v, None))
|
|
|
|
.collect(),
|
|
|
|
".rag" => self.list_rags().into_iter().map(|v| (v, None)).collect(),
|
2024-06-21 22:54:03 +00:00
|
|
|
".agent" => list_agents().into_iter().map(|v| (v, None)).collect(),
|
|
|
|
".starter" => match &self.agent {
|
|
|
|
Some(agent) => agent
|
2024-06-13 11:41:54 +00:00
|
|
|
.conversation_staters()
|
2024-06-12 23:41:29 +00:00
|
|
|
.iter()
|
|
|
|
.map(|v| (v.clone(), None))
|
|
|
|
.collect(),
|
|
|
|
None => vec![],
|
|
|
|
},
|
2024-07-09 13:22:51 +00:00
|
|
|
".variable" => match &self.agent {
|
|
|
|
Some(agent) => agent
|
|
|
|
.variables()
|
|
|
|
.iter()
|
|
|
|
.map(|v| (v.name.clone(), Some(v.description.clone())))
|
|
|
|
.collect(),
|
|
|
|
None => vec![],
|
|
|
|
},
|
2024-06-11 03:46:29 +00:00
|
|
|
".set" => vec![
|
|
|
|
"max_output_tokens",
|
|
|
|
"temperature",
|
|
|
|
"top_p",
|
2024-07-05 11:49:08 +00:00
|
|
|
"dry_run",
|
2024-07-28 00:56:00 +00:00
|
|
|
"stream",
|
2024-06-11 03:46:29 +00:00
|
|
|
"save",
|
|
|
|
"save_session",
|
2024-07-05 11:49:08 +00:00
|
|
|
"compress_threshold",
|
|
|
|
"function_calling",
|
|
|
|
"use_tools",
|
|
|
|
"rag_reranker_model",
|
|
|
|
"rag_top_k",
|
2024-06-11 03:46:29 +00:00
|
|
|
"highlight",
|
|
|
|
]
|
|
|
|
.into_iter()
|
|
|
|
.map(|v| (format!("{v} "), None))
|
|
|
|
.collect(),
|
|
|
|
_ => vec![],
|
|
|
|
};
|
2024-07-12 12:25:06 +00:00
|
|
|
filter = args[0]
|
|
|
|
} else if cmd == ".set" && args.len() == 2 {
|
|
|
|
let candidates = match args[0] {
|
2024-06-11 03:46:29 +00:00
|
|
|
"max_output_tokens" => match self.model.max_output_tokens() {
|
|
|
|
Some(v) => vec![v.to_string()],
|
|
|
|
None => vec![],
|
|
|
|
},
|
2024-07-05 11:49:08 +00:00
|
|
|
"dry_run" => complete_bool(self.dry_run),
|
2024-07-28 00:56:00 +00:00
|
|
|
"stream" => complete_bool(self.stream),
|
2024-06-11 03:46:29 +00:00
|
|
|
"save" => complete_bool(self.save),
|
|
|
|
"save_session" => {
|
|
|
|
let save_session = if let Some(session) = &self.session {
|
|
|
|
session.save_session()
|
|
|
|
} else {
|
|
|
|
self.save_session
|
|
|
|
};
|
|
|
|
complete_option_bool(save_session)
|
|
|
|
}
|
2024-07-05 11:49:08 +00:00
|
|
|
"function_calling" => complete_bool(self.function_calling),
|
|
|
|
"use_tools" => {
|
|
|
|
let mut prefix = String::new();
|
|
|
|
if let Some((v, _)) = args[1].rsplit_once(',') {
|
|
|
|
prefix = format!("{v},");
|
|
|
|
}
|
|
|
|
let mut values = vec![];
|
|
|
|
if prefix.is_empty() {
|
|
|
|
values.push("all".to_string());
|
|
|
|
}
|
|
|
|
values.extend(self.mapping_tools.keys().map(|v| v.to_string()));
|
|
|
|
values.extend(self.functions.declarations().iter().map(|v| v.name.clone()));
|
|
|
|
values
|
|
|
|
.into_iter()
|
|
|
|
.filter(|v| !prefix.contains(&format!("{v},")))
|
|
|
|
.map(|v| format!("{prefix}{v}"))
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
"rag_reranker_model" => list_reranker_models(self).iter().map(|v| v.id()).collect(),
|
2024-06-11 03:46:29 +00:00
|
|
|
"highlight" => complete_bool(self.highlight),
|
|
|
|
_ => vec![],
|
|
|
|
};
|
2024-07-12 12:25:06 +00:00
|
|
|
values = candidates.into_iter().map(|v| (v, None)).collect();
|
|
|
|
filter = args[1];
|
|
|
|
} else if cmd == ".starter" && args.len() >= 2 {
|
|
|
|
if let Some(agent) = &self.agent {
|
|
|
|
values = agent
|
|
|
|
.conversation_staters()
|
|
|
|
.iter()
|
|
|
|
.filter_map(|v| v.strip_prefix(line).map(|x| (x.to_string(), None)))
|
|
|
|
.collect()
|
|
|
|
}
|
2024-06-11 03:46:29 +00:00
|
|
|
};
|
|
|
|
values
|
|
|
|
.into_iter()
|
|
|
|
.filter(|(value, _)| fuzzy_match(value, filter))
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn last_reply(&self) -> &str {
|
|
|
|
self.last_message
|
|
|
|
.as_ref()
|
2024-06-17 05:19:00 +00:00
|
|
|
.map(|(_, reply)| reply.as_str())
|
2024-06-11 03:46:29 +00:00
|
|
|
.unwrap_or_default()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn render_options(&self) -> Result<RenderOptions> {
|
2023-10-31 01:06:41 +00:00
|
|
|
let theme = if self.highlight {
|
|
|
|
let theme_mode = if self.light_theme { "light" } else { "dark" };
|
|
|
|
let theme_filename = format!("{theme_mode}.tmTheme");
|
|
|
|
let theme_path = Self::local_path(&theme_filename)?;
|
|
|
|
if theme_path.exists() {
|
|
|
|
let theme = ThemeSet::get_theme(&theme_path)
|
|
|
|
.with_context(|| format!("Invalid theme at {}", theme_path.display()))?;
|
|
|
|
Some(theme)
|
|
|
|
} else {
|
|
|
|
let theme = if self.light_theme {
|
|
|
|
bincode::deserialize_from(LIGHT_THEME).expect("Invalid builtin light theme")
|
|
|
|
} else {
|
|
|
|
bincode::deserialize_from(DARK_THEME).expect("Invalid builtin dark theme")
|
|
|
|
};
|
|
|
|
Some(theme)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2024-06-05 22:08:41 +00:00
|
|
|
let wrap = if *IS_STDOUT_TERMINAL {
|
2023-10-30 09:28:10 +00:00
|
|
|
self.wrap.clone()
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2024-03-19 03:27:20 +00:00
|
|
|
let truecolor = matches!(
|
|
|
|
env::var("COLORTERM").as_ref().map(|v| v.as_str()),
|
|
|
|
Ok("truecolor")
|
|
|
|
);
|
|
|
|
Ok(RenderOptions::new(theme, wrap, self.wrap_code, truecolor))
|
2023-03-11 13:45:34 +00:00
|
|
|
}
|
|
|
|
|
2023-12-24 08:04:18 +00:00
|
|
|
pub fn render_prompt_left(&self) -> String {
|
|
|
|
let variables = self.generate_prompt_context();
|
2024-04-23 06:32:06 +00:00
|
|
|
let left_prompt = self.left_prompt.as_deref().unwrap_or(LEFT_PROMPT);
|
|
|
|
render_prompt(left_prompt, &variables)
|
2023-12-24 08:04:18 +00:00
|
|
|
}
|
|
|
|
|
2023-11-01 02:28:54 +00:00
|
|
|
pub fn render_prompt_right(&self) -> String {
|
2023-12-24 08:04:18 +00:00
|
|
|
let variables = self.generate_prompt_context();
|
2024-04-23 06:32:06 +00:00
|
|
|
let right_prompt = self.right_prompt.as_deref().unwrap_or(RIGHT_PROMPT);
|
|
|
|
render_prompt(right_prompt, &variables)
|
2023-11-01 02:28:54 +00:00
|
|
|
}
|
|
|
|
|
2024-07-28 00:56:00 +00:00
|
|
|
pub fn print_markdown(&self, text: &str) -> Result<()> {
|
|
|
|
if *IS_STDOUT_TERMINAL {
|
|
|
|
let render_options = self.render_options()?;
|
|
|
|
let mut markdown_render = MarkdownRender::init(render_options)?;
|
|
|
|
println!("{}", markdown_render.render(text));
|
|
|
|
} else {
|
|
|
|
println!("{text}");
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-12-24 08:04:18 +00:00
|
|
|
fn generate_prompt_context(&self) -> HashMap<&str, String> {
|
|
|
|
let mut output = HashMap::new();
|
2024-06-11 03:00:12 +00:00
|
|
|
let role = self.extract_role();
|
|
|
|
output.insert("model", role.model().id());
|
|
|
|
output.insert("client_name", role.model().client_name().to_string());
|
|
|
|
output.insert("model_name", role.model().name().to_string());
|
2023-12-24 08:04:18 +00:00
|
|
|
output.insert(
|
2024-03-06 00:35:40 +00:00
|
|
|
"max_input_tokens",
|
2024-06-11 03:00:12 +00:00
|
|
|
role.model()
|
2024-05-18 11:06:21 +00:00
|
|
|
.max_input_tokens()
|
|
|
|
.unwrap_or_default()
|
|
|
|
.to_string(),
|
2023-12-24 08:04:18 +00:00
|
|
|
);
|
2024-06-11 03:00:12 +00:00
|
|
|
if let Some(temperature) = role.temperature() {
|
2023-12-24 08:04:18 +00:00
|
|
|
if temperature != 0.0 {
|
|
|
|
output.insert("temperature", temperature.to_string());
|
|
|
|
}
|
|
|
|
}
|
2024-06-11 03:00:12 +00:00
|
|
|
if let Some(top_p) = role.top_p() {
|
2024-04-24 08:12:38 +00:00
|
|
|
if top_p != 0.0 {
|
|
|
|
output.insert("top_p", top_p.to_string());
|
|
|
|
}
|
|
|
|
}
|
2023-12-24 08:04:18 +00:00
|
|
|
if self.dry_run {
|
|
|
|
output.insert("dry_run", "true".to_string());
|
|
|
|
}
|
2024-07-28 00:56:00 +00:00
|
|
|
if self.stream {
|
|
|
|
output.insert("stream", "true".to_string());
|
|
|
|
}
|
2023-12-24 08:04:18 +00:00
|
|
|
if self.save {
|
|
|
|
output.insert("save", "true".to_string());
|
|
|
|
}
|
|
|
|
if let Some(wrap) = &self.wrap {
|
|
|
|
if wrap != "no" {
|
|
|
|
output.insert("wrap", wrap.clone());
|
|
|
|
}
|
|
|
|
}
|
2024-06-11 03:00:12 +00:00
|
|
|
if !role.is_derived() {
|
|
|
|
output.insert("role", role.name().to_string());
|
2023-12-24 08:04:18 +00:00
|
|
|
}
|
|
|
|
if let Some(session) = &self.session {
|
|
|
|
output.insert("session", session.name().to_string());
|
2024-06-11 03:00:12 +00:00
|
|
|
output.insert("dirty", session.dirty().to_string());
|
|
|
|
let (tokens, percent) = session.tokens_usage();
|
2023-12-24 08:04:18 +00:00
|
|
|
output.insert("consume_tokens", tokens.to_string());
|
|
|
|
output.insert("consume_percent", percent.to_string());
|
|
|
|
output.insert("user_messages_len", session.user_messages_len().to_string());
|
|
|
|
}
|
2024-06-05 01:02:23 +00:00
|
|
|
if let Some(rag) = &self.rag {
|
|
|
|
output.insert("rag", rag.name().to_string());
|
|
|
|
}
|
2024-06-21 22:54:03 +00:00
|
|
|
if let Some(agent) = &self.agent {
|
|
|
|
output.insert("agent", agent.name().to_string());
|
2024-06-11 03:00:12 +00:00
|
|
|
}
|
2023-12-24 08:04:18 +00:00
|
|
|
|
|
|
|
if self.highlight {
|
|
|
|
output.insert("color.reset", "\u{1b}[0m".to_string());
|
|
|
|
output.insert("color.black", "\u{1b}[30m".to_string());
|
|
|
|
output.insert("color.dark_gray", "\u{1b}[90m".to_string());
|
|
|
|
output.insert("color.red", "\u{1b}[31m".to_string());
|
|
|
|
output.insert("color.light_red", "\u{1b}[91m".to_string());
|
|
|
|
output.insert("color.green", "\u{1b}[32m".to_string());
|
|
|
|
output.insert("color.light_green", "\u{1b}[92m".to_string());
|
|
|
|
output.insert("color.yellow", "\u{1b}[33m".to_string());
|
|
|
|
output.insert("color.light_yellow", "\u{1b}[93m".to_string());
|
|
|
|
output.insert("color.blue", "\u{1b}[34m".to_string());
|
|
|
|
output.insert("color.light_blue", "\u{1b}[94m".to_string());
|
|
|
|
output.insert("color.purple", "\u{1b}[35m".to_string());
|
|
|
|
output.insert("color.light_purple", "\u{1b}[95m".to_string());
|
|
|
|
output.insert("color.magenta", "\u{1b}[35m".to_string());
|
|
|
|
output.insert("color.light_magenta", "\u{1b}[95m".to_string());
|
|
|
|
output.insert("color.cyan", "\u{1b}[36m".to_string());
|
|
|
|
output.insert("color.light_cyan", "\u{1b}[96m".to_string());
|
|
|
|
output.insert("color.white", "\u{1b}[37m".to_string());
|
|
|
|
output.insert("color.light_gray", "\u{1b}[97m".to_string());
|
|
|
|
}
|
|
|
|
|
|
|
|
output
|
|
|
|
}
|
|
|
|
|
2024-06-17 01:14:54 +00:00
|
|
|
pub fn before_chat_completion(&mut self, input: &Input) -> Result<()> {
|
2024-06-17 05:19:00 +00:00
|
|
|
self.last_message = Some((input.clone(), String::new()));
|
2024-06-17 01:14:54 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn after_chat_completion(
|
|
|
|
&mut self,
|
2024-06-27 00:21:49 +00:00
|
|
|
input: &Input,
|
2024-06-17 01:14:54 +00:00
|
|
|
output: &str,
|
|
|
|
tool_results: &[ToolResult],
|
|
|
|
) -> Result<()> {
|
|
|
|
if self.dry_run || output.is_empty() || !tool_results.is_empty() {
|
2024-06-17 05:19:00 +00:00
|
|
|
self.last_message = None;
|
2024-06-17 01:14:54 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
2024-06-17 05:19:00 +00:00
|
|
|
self.last_message = Some((input.clone(), output.to_string()));
|
|
|
|
self.save_message(input, output)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-06-17 01:14:54 +00:00
|
|
|
|
2024-06-27 00:21:49 +00:00
|
|
|
fn save_message(&mut self, input: &Input, output: &str) -> Result<()> {
|
|
|
|
let mut input = input.clone();
|
|
|
|
input.clear_patch();
|
2024-06-17 01:14:54 +00:00
|
|
|
if let Some(session) = input.session_mut(&mut self.session) {
|
2024-06-27 00:21:49 +00:00
|
|
|
session.add_message(&input, output)?;
|
2024-06-17 01:14:54 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if !self.save {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
let mut file = self.open_message_file()?;
|
|
|
|
if output.is_empty() || !self.save {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
let timestamp = now();
|
|
|
|
let summary = input.summary();
|
|
|
|
let input_markdown = input.render();
|
2024-06-21 22:54:03 +00:00
|
|
|
let scope = if self.agent.is_none() {
|
2024-06-17 01:14:54 +00:00
|
|
|
let role_name = if input.role().is_derived() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(input.role().name())
|
|
|
|
};
|
|
|
|
match (role_name, input.rag_name()) {
|
|
|
|
(Some(role), Some(rag_name)) => format!(" ({role}#{rag_name})"),
|
|
|
|
(Some(role), _) => format!(" ({role})"),
|
|
|
|
(None, Some(rag_name)) => format!(" (#{rag_name})"),
|
|
|
|
_ => String::new(),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
String::new()
|
|
|
|
};
|
|
|
|
let output = format!("# CHAT: {summary} [{timestamp}]{scope}\n{input_markdown}\n--------\n{output}\n--------\n\n",);
|
|
|
|
file.write_all(output.as_bytes())
|
|
|
|
.with_context(|| "Failed to save message")
|
|
|
|
}
|
|
|
|
|
2023-03-08 03:27:51 +00:00
|
|
|
fn open_message_file(&self) -> Result<File> {
|
2024-06-11 03:00:12 +00:00
|
|
|
let path = self.messages_file()?;
|
2023-03-11 03:26:24 +00:00
|
|
|
ensure_parent_exists(&path)?;
|
2023-03-08 03:27:51 +00:00
|
|
|
OpenOptions::new()
|
|
|
|
.create(true)
|
|
|
|
.append(true)
|
|
|
|
.open(&path)
|
|
|
|
.with_context(|| format!("Failed to create/append {}", path.display()))
|
|
|
|
}
|
|
|
|
|
2024-07-27 07:50:08 +00:00
|
|
|
fn load_from_file(config_path: &Path) -> Result<Self> {
|
2024-05-18 11:06:21 +00:00
|
|
|
let content = read_to_string(config_path)
|
|
|
|
.with_context(|| format!("Failed to load config at {}", config_path.display()))?;
|
2024-04-30 04:52:58 +00:00
|
|
|
let config: Self = serde_yaml::from_str(&content).map_err(|err| {
|
|
|
|
let err_msg = err.to_string();
|
|
|
|
let err_msg = if err_msg.starts_with(&format!("{}: ", CLIENTS_FIELD)) {
|
|
|
|
// location is incorrect, get rid of it
|
|
|
|
err_msg
|
|
|
|
.split_once(" at line")
|
|
|
|
.map(|(v, _)| {
|
|
|
|
format!("{v} (Sorry for being unable to provide an exact location)")
|
|
|
|
})
|
|
|
|
.unwrap_or_else(|| "clients: invalid value".into())
|
|
|
|
} else {
|
|
|
|
err_msg
|
|
|
|
};
|
|
|
|
anyhow!("{err_msg}")
|
|
|
|
})?;
|
|
|
|
|
2024-04-28 05:28:24 +00:00
|
|
|
Ok(config)
|
|
|
|
}
|
2023-03-11 03:26:24 +00:00
|
|
|
|
2024-07-27 07:50:08 +00:00
|
|
|
fn load_dynamic(model_id: &str) -> Result<Self> {
|
|
|
|
let platform = match model_id.split_once(':') {
|
|
|
|
Some((v, _)) => v,
|
|
|
|
_ => model_id,
|
|
|
|
};
|
2024-04-30 04:52:58 +00:00
|
|
|
let is_openai_compatible = OPENAI_COMPATIBLE_PLATFORMS
|
|
|
|
.into_iter()
|
|
|
|
.any(|(name, _)| platform == name);
|
|
|
|
let client = if is_openai_compatible {
|
|
|
|
json!({ "type": "openai-compatible", "name": platform })
|
|
|
|
} else {
|
|
|
|
json!({ "type": platform })
|
|
|
|
};
|
|
|
|
let config = json!({
|
2024-07-27 07:50:08 +00:00
|
|
|
"model": model_id.to_string(),
|
2024-04-30 04:52:58 +00:00
|
|
|
"save": false,
|
|
|
|
"clients": vec![client],
|
|
|
|
});
|
|
|
|
let config =
|
|
|
|
serde_json::from_value(config).with_context(|| "Failed to load config from env")?;
|
2023-03-11 03:26:24 +00:00
|
|
|
Ok(config)
|
|
|
|
}
|
|
|
|
|
2024-07-27 06:52:13 +00:00
|
|
|
fn load_envs(&mut self) {
|
2024-07-30 13:38:25 +00:00
|
|
|
if let Ok(v) = env::var(get_env_name("model")) {
|
|
|
|
self.model_id = v;
|
|
|
|
}
|
2024-07-27 06:52:13 +00:00
|
|
|
if let Some(v) = read_env_value::<f64>("temperature") {
|
|
|
|
self.temperature = v;
|
|
|
|
}
|
|
|
|
if let Some(v) = read_env_value::<f64>("top_p") {
|
|
|
|
self.top_p = v;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(Some(v)) = read_env_bool("dry_run") {
|
|
|
|
self.dry_run = v;
|
|
|
|
}
|
2024-07-28 00:56:00 +00:00
|
|
|
if let Some(Some(v)) = read_env_bool("stream") {
|
|
|
|
self.stream = v;
|
|
|
|
}
|
2024-07-27 06:52:13 +00:00
|
|
|
if let Some(Some(v)) = read_env_bool("save") {
|
|
|
|
self.save = v;
|
|
|
|
}
|
|
|
|
if let Ok(v) = env::var(get_env_name("keybindings")) {
|
|
|
|
if v == "vi" {
|
|
|
|
self.keybindings = v;
|
|
|
|
}
|
|
|
|
}
|
2024-08-01 07:01:34 +00:00
|
|
|
if let Some(v) = read_env_value::<String>("editor") {
|
|
|
|
self.editor = v;
|
2024-07-27 06:52:13 +00:00
|
|
|
}
|
|
|
|
if let Some(v) = read_env_value::<String>("wrap") {
|
|
|
|
self.wrap = v;
|
|
|
|
}
|
|
|
|
if let Some(Some(v)) = read_env_bool("wrap_code") {
|
|
|
|
self.wrap_code = v;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(v) = read_env_value::<String>("prelude") {
|
|
|
|
self.prelude = v;
|
|
|
|
}
|
|
|
|
if let Some(v) = read_env_value::<String>("repl_prelude") {
|
|
|
|
self.repl_prelude = v;
|
|
|
|
}
|
|
|
|
if let Some(v) = read_env_value::<String>("agent_prelude") {
|
|
|
|
self.agent_prelude = v;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(v) = read_env_bool("save_session") {
|
|
|
|
self.save_session = v;
|
|
|
|
}
|
|
|
|
if let Some(Some(v)) = read_env_value::<usize>("compress_threshold") {
|
|
|
|
self.compress_threshold = v;
|
|
|
|
}
|
|
|
|
if let Some(v) = read_env_value::<String>("summarize_prompt") {
|
|
|
|
self.summarize_prompt = v;
|
|
|
|
}
|
|
|
|
if let Some(v) = read_env_value::<String>("summary_prompt") {
|
|
|
|
self.summary_prompt = v;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(Some(v)) = read_env_bool("function_calling") {
|
|
|
|
self.function_calling = v;
|
|
|
|
}
|
|
|
|
if let Ok(v) = env::var(get_env_name("mapping_tools")) {
|
|
|
|
if let Ok(v) = serde_json::from_str(&v) {
|
|
|
|
self.mapping_tools = v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(v) = read_env_value::<String>("use_tools") {
|
|
|
|
self.use_tools = v;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(v) = read_env_value::<String>("rag_embedding_model") {
|
|
|
|
self.rag_embedding_model = v;
|
|
|
|
}
|
|
|
|
if let Some(v) = read_env_value::<String>("rag_reranker_model") {
|
|
|
|
self.rag_reranker_model = v;
|
|
|
|
}
|
|
|
|
if let Some(Some(v)) = read_env_value::<usize>("rag_top_k") {
|
|
|
|
self.rag_top_k = v;
|
|
|
|
}
|
|
|
|
if let Some(v) = read_env_value::<usize>("rag_chunk_size") {
|
|
|
|
self.rag_chunk_size = v;
|
|
|
|
}
|
|
|
|
if let Some(v) = read_env_value::<usize>("rag_chunk_overlap") {
|
|
|
|
self.rag_chunk_overlap = v;
|
|
|
|
}
|
|
|
|
if let Some(Some(v)) = read_env_value::<f32>("rag_min_score_vector_search") {
|
|
|
|
self.rag_min_score_vector_search = v;
|
|
|
|
}
|
|
|
|
if let Some(Some(v)) = read_env_value::<f32>("rag_min_score_keyword_search") {
|
|
|
|
self.rag_min_score_keyword_search = v;
|
|
|
|
}
|
|
|
|
if let Some(Some(v)) = read_env_value::<f32>("rag_min_score_rerank") {
|
|
|
|
self.rag_min_score_rerank = v;
|
|
|
|
}
|
|
|
|
if let Some(v) = read_env_value::<String>("rag_template") {
|
|
|
|
self.rag_template = v;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Ok(v) = env::var(get_env_name("document_loaders")) {
|
|
|
|
if let Ok(v) = serde_json::from_str(&v) {
|
|
|
|
self.document_loaders = v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(Some(v)) = read_env_bool("highlight") {
|
|
|
|
self.highlight = v;
|
|
|
|
}
|
|
|
|
if let Ok(value) = env::var("NO_COLOR") {
|
|
|
|
if let Some(false) = parse_bool(&value) {
|
|
|
|
self.highlight = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(Some(v)) = read_env_bool("light_theme") {
|
|
|
|
self.light_theme = v;
|
|
|
|
} else if !self.light_theme {
|
|
|
|
if let Ok(v) = env::var("COLORFGBG") {
|
|
|
|
if let Some(v) = light_theme_from_colorfgbg(&v) {
|
|
|
|
self.light_theme = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(v) = read_env_value::<String>("left_prompt") {
|
|
|
|
self.left_prompt = v;
|
|
|
|
}
|
|
|
|
if let Some(v) = read_env_value::<String>("right_prompt") {
|
|
|
|
self.right_prompt = v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-02 11:06:18 +00:00
|
|
|
fn load_functions(&mut self) -> Result<()> {
|
|
|
|
self.functions = Functions::init(&Self::functions_file()?)?;
|
|
|
|
if self.functions.is_empty() {
|
|
|
|
self.function_calling = false;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-11-02 22:52:57 +00:00
|
|
|
fn setup_model(&mut self) -> Result<()> {
|
2024-07-27 06:52:13 +00:00
|
|
|
let mut model_id = self.model_id.clone();
|
2024-07-09 03:17:13 +00:00
|
|
|
if model_id.is_empty() {
|
2024-06-05 01:02:23 +00:00
|
|
|
let models = list_chat_models(self);
|
2024-05-14 04:43:16 +00:00
|
|
|
if models.is_empty() {
|
|
|
|
bail!("No available model");
|
2023-10-31 08:50:08 +00:00
|
|
|
}
|
2024-07-09 03:17:13 +00:00
|
|
|
model_id = models[0].id()
|
2023-10-31 08:50:08 +00:00
|
|
|
};
|
2024-05-14 04:43:16 +00:00
|
|
|
self.set_model(&model_id)?;
|
2024-06-05 21:27:21 +00:00
|
|
|
self.model_id = model_id;
|
2023-10-31 08:50:08 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-06-27 22:24:20 +00:00
|
|
|
fn setup_document_loaders(&mut self) {
|
2024-08-12 14:31:59 +00:00
|
|
|
[("pdf", "pdftotext $1 -"), ("docx", "pandoc --to plain $1")]
|
|
|
|
.into_iter()
|
|
|
|
.for_each(|(k, v)| {
|
|
|
|
let (k, v) = (k.to_string(), v.to_string());
|
|
|
|
self.document_loaders.entry(k).or_insert(v);
|
|
|
|
});
|
2024-06-26 00:18:58 +00:00
|
|
|
}
|
2023-03-02 11:52:11 +00:00
|
|
|
}
|
|
|
|
|
2024-07-04 06:40:52 +00:00
|
|
|
pub fn load_env_file() -> Result<()> {
|
|
|
|
let env_file_path = Config::env_file()?;
|
|
|
|
let contents = match read_to_string(&env_file_path) {
|
|
|
|
Ok(v) => v,
|
|
|
|
Err(_) => return Ok(()),
|
|
|
|
};
|
|
|
|
debug!("Use env file '{}'", env_file_path.display());
|
|
|
|
for line in contents.lines() {
|
|
|
|
let line = line.trim();
|
|
|
|
if line.starts_with('#') || line.is_empty() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if let Some((key, value)) = line.split_once('=') {
|
2024-07-27 06:52:13 +00:00
|
|
|
env::set_var(key.trim(), value.trim());
|
2024-07-04 06:40:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-04-23 23:16:56 +00:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
pub enum WorkingMode {
|
|
|
|
Command,
|
|
|
|
Repl,
|
|
|
|
Serve,
|
|
|
|
}
|
|
|
|
|
2024-06-17 11:54:24 +00:00
|
|
|
impl WorkingMode {
|
|
|
|
pub fn is_repl(&self) -> bool {
|
|
|
|
*self == WorkingMode::Repl
|
|
|
|
}
|
2024-06-21 11:45:55 +00:00
|
|
|
pub fn is_serve(&self) -> bool {
|
|
|
|
*self == WorkingMode::Serve
|
|
|
|
}
|
2024-06-17 11:54:24 +00:00
|
|
|
}
|
|
|
|
|
2024-06-02 11:27:41 +00:00
|
|
|
bitflags::bitflags! {
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
pub struct StateFlags: u32 {
|
|
|
|
const ROLE = 1 << 0;
|
|
|
|
const SESSION_EMPTY = 1 << 1;
|
|
|
|
const SESSION = 1 << 2;
|
2024-06-05 01:02:23 +00:00
|
|
|
const RAG = 1 << 3;
|
2024-06-23 15:09:47 +00:00
|
|
|
const AGENT = 1 << 4;
|
2024-03-03 06:52:15 +00:00
|
|
|
}
|
2024-06-02 11:27:41 +00:00
|
|
|
}
|
2024-03-03 06:52:15 +00:00
|
|
|
|
2024-06-02 11:27:41 +00:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
pub enum AssertState {
|
|
|
|
True(StateFlags),
|
|
|
|
False(StateFlags),
|
2024-06-11 03:00:12 +00:00
|
|
|
TrueFalse(StateFlags, StateFlags),
|
|
|
|
Equal(StateFlags),
|
2024-06-02 11:27:41 +00:00
|
|
|
}
|
2024-05-14 04:43:16 +00:00
|
|
|
|
2024-06-02 11:27:41 +00:00
|
|
|
impl AssertState {
|
2024-06-11 03:00:12 +00:00
|
|
|
pub fn pass() -> Self {
|
2024-06-02 11:27:41 +00:00
|
|
|
AssertState::False(StateFlags::empty())
|
2024-05-14 04:43:16 +00:00
|
|
|
}
|
2024-06-11 03:00:12 +00:00
|
|
|
pub fn bare() -> Self {
|
|
|
|
AssertState::Equal(StateFlags::empty())
|
|
|
|
}
|
2024-03-03 06:52:15 +00:00
|
|
|
}
|
|
|
|
|
2023-03-03 09:53:29 +00:00
|
|
|
fn create_config_file(config_path: &Path) -> Result<()> {
|
|
|
|
let ans = Confirm::new("No config file, create a new one?")
|
|
|
|
.with_default(true)
|
2024-02-24 11:13:48 +00:00
|
|
|
.prompt()?;
|
2023-03-03 09:53:29 +00:00
|
|
|
if !ans {
|
2024-06-26 10:56:26 +00:00
|
|
|
process::exit(0);
|
2023-03-03 09:53:29 +00:00
|
|
|
}
|
|
|
|
|
2024-02-24 11:13:48 +00:00
|
|
|
let client = Select::new("Platform:", list_client_types()).prompt()?;
|
2023-10-26 08:42:54 +00:00
|
|
|
|
2023-11-01 02:28:54 +00:00
|
|
|
let mut config = serde_json::json!({});
|
2024-03-25 03:13:54 +00:00
|
|
|
let (model, clients_config) = create_client_config(client)?;
|
|
|
|
config["model"] = model.into();
|
|
|
|
config[CLIENTS_FIELD] = clients_config;
|
2023-10-26 08:42:54 +00:00
|
|
|
|
2023-11-01 02:28:54 +00:00
|
|
|
let config_data = serde_yaml::to_string(&config).with_context(|| "Failed to create config")?;
|
2023-03-03 09:53:29 +00:00
|
|
|
|
2023-03-11 03:26:24 +00:00
|
|
|
ensure_parent_exists(config_path)?;
|
2023-11-01 02:28:54 +00:00
|
|
|
std::fs::write(config_path, config_data).with_context(|| "Failed to write to config file")?;
|
2023-05-04 08:29:34 +00:00
|
|
|
#[cfg(unix)]
|
|
|
|
{
|
|
|
|
use std::os::unix::prelude::PermissionsExt;
|
|
|
|
let perms = std::fs::Permissions::from_mode(0o600);
|
|
|
|
std::fs::set_permissions(config_path, perms)?;
|
|
|
|
}
|
2023-10-31 09:34:16 +00:00
|
|
|
|
2024-06-05 01:02:23 +00:00
|
|
|
println!("✨ Saved config file to '{}'\n", config_path.display());
|
2023-10-31 09:34:16 +00:00
|
|
|
|
2023-03-03 09:53:29 +00:00
|
|
|
Ok(())
|
2023-03-02 11:52:11 +00:00
|
|
|
}
|
2023-03-05 11:48:22 +00:00
|
|
|
|
2024-06-05 01:02:23 +00:00
|
|
|
pub(crate) fn ensure_parent_exists(path: &Path) -> Result<()> {
|
2023-03-11 03:26:24 +00:00
|
|
|
if path.exists() {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
let parent = path
|
|
|
|
.parent()
|
2024-07-09 13:22:51 +00:00
|
|
|
.ok_or_else(|| anyhow!("Failed to write to '{}', No parent path", path.display()))?;
|
2023-03-11 03:26:24 +00:00
|
|
|
if !parent.exists() {
|
|
|
|
create_dir_all(parent).with_context(|| {
|
|
|
|
format!(
|
2024-07-09 13:22:51 +00:00
|
|
|
"Failed to write to '{}', Cannot create parent directory",
|
2023-03-11 03:26:24 +00:00
|
|
|
path.display()
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-07-27 06:52:13 +00:00
|
|
|
fn read_env_value<T>(key: &str) -> Option<Option<T>>
|
|
|
|
where
|
|
|
|
T: std::str::FromStr,
|
|
|
|
{
|
|
|
|
let value = env::var(get_env_name(key)).ok()?;
|
|
|
|
let value = parse_value(&value).ok()?;
|
|
|
|
Some(value)
|
2023-03-11 10:53:36 +00:00
|
|
|
}
|
2023-11-02 13:38:01 +00:00
|
|
|
|
2024-03-27 02:02:09 +00:00
|
|
|
fn parse_value<T>(value: &str) -> Result<Option<T>>
|
|
|
|
where
|
|
|
|
T: std::str::FromStr,
|
|
|
|
{
|
|
|
|
let value = if value == "null" {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
let value = match value.parse() {
|
|
|
|
Ok(value) => value,
|
|
|
|
Err(_) => bail!("Invalid value '{}'", value),
|
|
|
|
};
|
|
|
|
Some(value)
|
|
|
|
};
|
|
|
|
Ok(value)
|
|
|
|
}
|
|
|
|
|
2024-07-27 06:52:13 +00:00
|
|
|
fn read_env_bool(key: &str) -> Option<Option<bool>> {
|
|
|
|
let value = env::var(get_env_name(key)).ok()?;
|
|
|
|
Some(parse_bool(&value))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_bool(value: &str) -> Option<bool> {
|
|
|
|
match value {
|
|
|
|
"1" | "true" => Some(true),
|
|
|
|
"0" | "false" => Some(false),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-27 02:02:09 +00:00
|
|
|
fn complete_bool(value: bool) -> Vec<String> {
|
|
|
|
vec![(!value).to_string()]
|
|
|
|
}
|
|
|
|
|
|
|
|
fn complete_option_bool(value: Option<bool>) -> Vec<String> {
|
|
|
|
match value {
|
|
|
|
Some(true) => vec!["false".to_string(), "null".to_string()],
|
|
|
|
Some(false) => vec!["true".to_string(), "null".to_string()],
|
|
|
|
None => vec!["true".to_string(), "false".to_string()],
|
|
|
|
}
|
|
|
|
}
|