Skip to content

Commit 0f5a0cd

Browse files
committed
Merge branch 'main' into persistant-conv-invariant
2 parents 5f195de + 0a43fd2 commit 0f5a0cd

File tree

15 files changed

+895
-45
lines changed

15 files changed

+895
-45
lines changed

crates/chat-cli/src/cli/chat/cli.rs

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
use clap::Parser;
1+
use std::collections::HashMap;
2+
3+
use clap::{
4+
Args,
5+
Parser,
6+
Subcommand,
7+
ValueEnum,
8+
};
29

310
#[derive(Debug, Clone, PartialEq, Eq, Default, Parser)]
411
pub struct Chat {
@@ -10,6 +17,9 @@ pub struct Chat {
1017
/// prompt requests permissions to use a tool, unless --trust-all-tools is also used.
1118
#[arg(long)]
1219
pub no_interactive: bool,
20+
/// Start a new conversation and overwrites any previous conversation from this directory.
21+
#[arg(long)]
22+
pub new: bool,
1323
/// The first question to ask
1424
pub input: Option<String>,
1525
/// Context profile to use
@@ -23,3 +33,127 @@ pub struct Chat {
2333
#[arg(long, value_delimiter = ',', value_name = "TOOL_NAMES")]
2434
pub trust_tools: Option<Vec<String>>,
2535
}
36+
37+
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
38+
pub enum Mcp {
39+
/// Add or replace a configured server
40+
Add(McpAdd),
41+
/// Remove a server from the MCP configuration
42+
#[command(alias = "rm")]
43+
Remove(McpRemove),
44+
/// List configured servers
45+
List(McpList),
46+
/// Import a server configuration from another file
47+
Import(McpImport),
48+
/// Get the status of a configured server
49+
Status {
50+
#[arg(long)]
51+
name: String,
52+
},
53+
}
54+
55+
#[derive(Debug, Clone, PartialEq, Eq, Args)]
56+
pub struct McpAdd {
57+
/// Name for the server
58+
#[arg(long)]
59+
pub name: String,
60+
/// The command used to launch the server
61+
#[arg(long)]
62+
pub command: String,
63+
/// Where to add the server to. For profile scope, the name of the profile must specified with
64+
/// --profile.
65+
#[arg(long, value_enum)]
66+
pub scope: Option<Scope>,
67+
/// Name of the profile to add the server config to. Not compatible with workspace scope or
68+
/// global scope.
69+
#[arg(long)]
70+
pub profile: Option<String>,
71+
/// Environment variables to use when launching the server
72+
#[arg(long, value_parser = parse_env_vars)]
73+
pub env: Vec<HashMap<String, String>>,
74+
/// Server launch timeout, in milliseconds
75+
#[arg(long)]
76+
pub timeout: Option<u64>,
77+
/// Overwrite an existing server with the same name
78+
#[arg(long, default_value_t = false)]
79+
pub force: bool,
80+
}
81+
82+
#[derive(Debug, Clone, PartialEq, Eq, Args)]
83+
pub struct McpRemove {
84+
#[arg(long)]
85+
pub name: String,
86+
#[arg(long, value_enum)]
87+
pub scope: Option<Scope>,
88+
#[arg(long)]
89+
pub profile: Option<String>,
90+
}
91+
92+
#[derive(Debug, Clone, PartialEq, Eq, Args)]
93+
pub struct McpList {
94+
#[arg(value_enum)]
95+
pub scope: Option<Scope>,
96+
#[arg(long)]
97+
pub profile: Option<String>,
98+
}
99+
100+
#[derive(Debug, Clone, PartialEq, Eq, Args)]
101+
pub struct McpImport {
102+
#[arg(long)]
103+
pub file: String,
104+
#[arg(value_enum)]
105+
pub scope: Option<Scope>,
106+
#[arg(long)]
107+
pub profile: Option<String>,
108+
/// Overwrite an existing server with the same name
109+
#[arg(long, default_value_t = false)]
110+
pub force: bool,
111+
}
112+
113+
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
114+
pub enum Scope {
115+
Workspace,
116+
Profile,
117+
Global,
118+
}
119+
120+
impl std::fmt::Display for Scope {
121+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122+
match self {
123+
Scope::Workspace => write!(f, "workspace"),
124+
Scope::Profile => write!(f, "profile"),
125+
Scope::Global => write!(f, "global"),
126+
}
127+
}
128+
}
129+
130+
#[derive(Debug)]
131+
struct EnvVarParseError(String);
132+
133+
impl std::fmt::Display for EnvVarParseError {
134+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135+
write!(f, "Failed to parse environment variables: {}", self.0)
136+
}
137+
}
138+
139+
impl std::error::Error for EnvVarParseError {}
140+
141+
fn parse_env_vars(arg: &str) -> Result<HashMap<String, String>, EnvVarParseError> {
142+
let mut vars = HashMap::new();
143+
144+
for pair in arg.split(",") {
145+
match pair.split_once('=') {
146+
Some((key, value)) => {
147+
vars.insert(key.trim().to_string(), value.trim().to_string());
148+
},
149+
None => {
150+
return Err(EnvVarParseError(format!(
151+
"Invalid environment variable '{}'. Expected 'name=value'",
152+
pair
153+
)));
154+
},
155+
}
156+
}
157+
158+
Ok(vars)
159+
}

crates/chat-cli/src/cli/chat/context.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pub struct ContextManager {
5959
/// Context configuration for the current profile.
6060
pub profile_config: ContextConfig,
6161

62+
#[serde(skip)]
6263
pub hook_executor: HookExecutor,
6364
}
6465

@@ -128,6 +129,13 @@ impl ContextManager {
128129
Ok(())
129130
}
130131

132+
/// Reloads the global and profile config from disk.
133+
pub async fn reload_config(&mut self) -> Result<()> {
134+
self.global_config = load_global_config(&self.ctx).await?;
135+
self.profile_config = load_profile_config(&self.ctx, &self.current_profile).await?;
136+
Ok(())
137+
}
138+
131139
/// Add paths to the context configuration.
132140
///
133141
/// # Arguments

crates/chat-cli/src/cli/chat/conversation_state.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,37 @@ impl ConversationState {
157157
}
158158
}
159159

160+
/// Reloads necessary fields after being deserialized. This should be called after
161+
/// deserialization.
162+
pub async fn reload_serialized_state(&mut self, ctx: Arc<Context>, updates: Option<SharedWriter>) {
163+
self.updates = updates;
164+
165+
// Try to reload ContextManager, but do not return an error if we fail.
166+
// TODO: Currently the failure modes around ContextManager is unclear, and we don't return
167+
// errors in most cases. Thus, we try to preserve the same behavior here and simply have
168+
// self.context_manager equal to None if any errors are encountered. This needs to be
169+
// refactored.
170+
let mut failed = false;
171+
if let Some(context_manager) = self.context_manager.as_mut() {
172+
match context_manager.reload_config().await {
173+
Ok(_) => (),
174+
Err(err) => {
175+
error!(?err, "failed to reload context config");
176+
match ContextManager::new(ctx, None).await {
177+
Ok(v) => *context_manager = v,
178+
Err(err) => {
179+
failed = true;
180+
error!(?err, "failed to construct context manager");
181+
},
182+
}
183+
},
184+
}
185+
}
186+
if failed {
187+
self.context_manager.take();
188+
}
189+
}
190+
160191
pub fn latest_summary(&self) -> Option<&str> {
161192
self.latest_summary.as_deref()
162193
}

crates/chat-cli/src/cli/chat/hooks.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,14 @@ pub enum HookTrigger {
119119
PerPrompt,
120120
}
121121

122-
#[derive(Debug, Clone, Serialize, Deserialize)]
122+
#[derive(Debug, Clone)]
123123
pub struct CachedHook {
124124
output: String,
125-
#[serde(skip)]
126125
expiry: Option<Instant>,
127126
}
128127

129128
/// Maps a hook name to a [`CachedHook`]
130-
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
129+
#[derive(Debug, Clone, Default)]
131130
pub struct HookExecutor {
132131
pub global_cache: HashMap<String, CachedHook>,
133132
pub profile_cache: HashMap<String, CachedHook>,

0 commit comments

Comments
 (0)