fix: don't silently fail on skill role composition extraction in llm nodes

This commit is contained in:
2026-06-04 09:09:55 -06:00
parent 97100bee29
commit 73a4499c68
9 changed files with 74 additions and 74 deletions
+31 -31
View File
@@ -38,10 +38,10 @@ pub struct Input {
} }
impl Input { impl Input {
pub fn from_str(ctx: &RequestContext, text: &str, role: Option<Role>) -> Self { pub fn from_str(ctx: &RequestContext, text: &str, role: Option<Role>) -> Result<Self> {
let (role, with_session, with_agent) = resolve_role(ctx, role); let (role, with_session, with_agent) = resolve_role(ctx, role)?;
let captured = capture_input_config(ctx, &role); let captured = capture_input_config(ctx, &role);
Self { Ok(Self {
app_config: Arc::clone(&ctx.app.config), app_config: Arc::clone(&ctx.app.config),
stream_enabled: captured.stream_enabled, stream_enabled: captured.stream_enabled,
session: captured.session, session: captured.session,
@@ -60,7 +60,7 @@ impl Input {
rag_name: None, rag_name: None,
with_session, with_session,
with_agent, with_agent,
} })
} }
pub async fn from_files( pub async fn from_files(
@@ -111,7 +111,7 @@ impl Input {
)); ));
} }
} }
let (role, with_session, with_agent) = resolve_role(ctx, role); let (role, with_session, with_agent) = resolve_role(ctx, role)?;
let captured = capture_input_config(ctx, &role); let captured = capture_input_config(ctx, &role);
Ok(Self { Ok(Self {
app_config: Arc::clone(&ctx.app.config), app_config: Arc::clone(&ctx.app.config),
@@ -398,14 +398,14 @@ impl Input {
} }
} }
fn resolve_role(ctx: &RequestContext, role: Option<Role>) -> (Role, bool, bool) { fn resolve_role(ctx: &RequestContext, role: Option<Role>) -> Result<(Role, bool, bool)> {
match role { match role {
Some(v) => (v, false, false), Some(v) => Ok((v, false, false)),
None => ( None => Ok((
ctx.extract_role(ctx.app.config.as_ref()), ctx.extract_role(ctx.app.config.as_ref())?,
ctx.session.is_some(), ctx.session.is_some(),
ctx.agent.is_some(), ctx.agent.is_some(),
), )),
} }
} }
@@ -600,7 +600,7 @@ mod tests {
fn resolve_role_with_explicit_role() { fn resolve_role_with_explicit_role() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let role = Role::new("custom", "be helpful"); let role = Role::new("custom", "be helpful");
let (resolved, with_session, with_agent) = resolve_role(&ctx, Some(role)); let (resolved, with_session, with_agent) = resolve_role(&ctx, Some(role)).unwrap();
assert_eq!(resolved.name(), "custom"); assert_eq!(resolved.name(), "custom");
assert!(!with_session); assert!(!with_session);
assert!(!with_agent); assert!(!with_agent);
@@ -609,7 +609,7 @@ mod tests {
#[test] #[test]
fn resolve_role_without_role_no_session_no_agent() { fn resolve_role_without_role_no_session_no_agent() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let (resolved, with_session, with_agent) = resolve_role(&ctx, None); let (resolved, with_session, with_agent) = resolve_role(&ctx, None).unwrap();
assert_eq!(resolved.name(), ""); assert_eq!(resolved.name(), "");
assert!(!with_session); assert!(!with_session);
assert!(!with_agent); assert!(!with_agent);
@@ -619,7 +619,7 @@ mod tests {
fn resolve_role_without_role_with_session() { fn resolve_role_without_role_with_session() {
let mut ctx = create_test_ctx(); let mut ctx = create_test_ctx();
ctx.session = Some(Session::default()); ctx.session = Some(Session::default());
let (_resolved, with_session, with_agent) = resolve_role(&ctx, None); let (_resolved, with_session, with_agent) = resolve_role(&ctx, None).unwrap();
assert!(with_session); assert!(with_session);
assert!(!with_agent); assert!(!with_agent);
} }
@@ -629,7 +629,7 @@ mod tests {
let mut ctx = create_test_ctx(); let mut ctx = create_test_ctx();
ctx.session = Some(Session::default()); ctx.session = Some(Session::default());
let role = Role::new("explicit", "prompt"); let role = Role::new("explicit", "prompt");
let (_resolved, with_session, _with_agent) = resolve_role(&ctx, Some(role)); let (_resolved, with_session, _with_agent) = resolve_role(&ctx, Some(role)).unwrap();
assert!(!with_session); assert!(!with_session);
} }
@@ -695,7 +695,7 @@ mod tests {
#[test] #[test]
fn input_from_str_captures_text() { fn input_from_str_captures_text() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let input = Input::from_str(&ctx, "hello world", None); let input = Input::from_str(&ctx, "hello world", None).unwrap();
assert_eq!(input.text(), "hello world"); assert_eq!(input.text(), "hello world");
} }
@@ -703,7 +703,7 @@ mod tests {
fn input_from_str_with_explicit_role() { fn input_from_str_with_explicit_role() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let role = Role::new("pirate", "you are a pirate"); let role = Role::new("pirate", "you are a pirate");
let input = Input::from_str(&ctx, "ahoy", Some(role)); let input = Input::from_str(&ctx, "ahoy", Some(role)).unwrap();
assert_eq!(input.role().name(), "pirate"); assert_eq!(input.role().name(), "pirate");
assert!(!input.with_agent()); assert!(!input.with_agent());
} }
@@ -715,28 +715,28 @@ mod tests {
config.stream = false; config.stream = false;
state.config = Arc::new(config); state.config = Arc::new(config);
let ctx = RequestContext::new(Arc::new(state), WorkingMode::Cmd); let ctx = RequestContext::new(Arc::new(state), WorkingMode::Cmd);
let input = Input::from_str(&ctx, "test", None); let input = Input::from_str(&ctx, "test", None).unwrap();
assert!(!input.stream_enabled); assert!(!input.stream_enabled);
} }
#[test] #[test]
fn input_is_empty_with_no_text_and_no_medias() { fn input_is_empty_with_no_text_and_no_medias() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let input = Input::from_str(&ctx, "", None); let input = Input::from_str(&ctx, "", None).unwrap();
assert!(input.is_empty()); assert!(input.is_empty());
} }
#[test] #[test]
fn input_is_not_empty_with_text() { fn input_is_not_empty_with_text() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let input = Input::from_str(&ctx, "hello", None); let input = Input::from_str(&ctx, "hello", None).unwrap();
assert!(!input.is_empty()); assert!(!input.is_empty());
} }
#[test] #[test]
fn input_set_text_changes_text() { fn input_set_text_changes_text() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let mut input = Input::from_str(&ctx, "original", None); let mut input = Input::from_str(&ctx, "original", None).unwrap();
input.set_text("modified".to_string()); input.set_text("modified".to_string());
assert_eq!(input.text(), "modified"); assert_eq!(input.text(), "modified");
} }
@@ -744,7 +744,7 @@ mod tests {
#[test] #[test]
fn input_text_returns_patched_when_set() { fn input_text_returns_patched_when_set() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let mut input = Input::from_str(&ctx, "original", None); let mut input = Input::from_str(&ctx, "original", None).unwrap();
input.patched_text = Some("patched".to_string()); input.patched_text = Some("patched".to_string());
assert_eq!(input.text(), "patched"); assert_eq!(input.text(), "patched");
} }
@@ -752,7 +752,7 @@ mod tests {
#[test] #[test]
fn input_clear_patch_restores_original() { fn input_clear_patch_restores_original() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let mut input = Input::from_str(&ctx, "original", None); let mut input = Input::from_str(&ctx, "original", None).unwrap();
input.patched_text = Some("patched".to_string()); input.patched_text = Some("patched".to_string());
input.clear_patch(); input.clear_patch();
assert_eq!(input.text(), "original"); assert_eq!(input.text(), "original");
@@ -761,7 +761,7 @@ mod tests {
#[test] #[test]
fn input_set_continue_output_accumulates() { fn input_set_continue_output_accumulates() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let mut input = Input::from_str(&ctx, "test", None); let mut input = Input::from_str(&ctx, "test", None).unwrap();
assert!(input.continue_output().is_none()); assert!(input.continue_output().is_none());
input.set_continue_output("first "); input.set_continue_output("first ");
assert_eq!(input.continue_output(), Some("first ")); assert_eq!(input.continue_output(), Some("first "));
@@ -772,7 +772,7 @@ mod tests {
#[test] #[test]
fn input_set_regenerate_sets_flag_and_clears_tool_calls() { fn input_set_regenerate_sets_flag_and_clears_tool_calls() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let mut input = Input::from_str(&ctx, "test", None); let mut input = Input::from_str(&ctx, "test", None).unwrap();
let role = input.role().clone(); let role = input.role().clone();
assert!(!input.regenerate()); assert!(!input.regenerate());
input.set_regenerate(role); input.set_regenerate(role);
@@ -784,7 +784,7 @@ mod tests {
fn input_summary_truncates_long_text() { fn input_summary_truncates_long_text() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let long_text = "a".repeat(200); let long_text = "a".repeat(200);
let input = Input::from_str(&ctx, &long_text, None); let input = Input::from_str(&ctx, &long_text, None).unwrap();
let summary = input.summary(); let summary = input.summary();
assert!(summary.len() < 200); assert!(summary.len() < 200);
assert!(summary.ends_with("...")); assert!(summary.ends_with("..."));
@@ -793,35 +793,35 @@ mod tests {
#[test] #[test]
fn input_summary_preserves_short_text() { fn input_summary_preserves_short_text() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let input = Input::from_str(&ctx, "short", None); let input = Input::from_str(&ctx, "short", None).unwrap();
assert_eq!(input.summary(), "short"); assert_eq!(input.summary(), "short");
} }
#[test] #[test]
fn input_raw_with_no_files() { fn input_raw_with_no_files() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let input = Input::from_str(&ctx, "hello", None); let input = Input::from_str(&ctx, "hello", None).unwrap();
assert_eq!(input.raw(), "hello"); assert_eq!(input.raw(), "hello");
} }
#[test] #[test]
fn input_render_with_no_medias() { fn input_render_with_no_medias() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let input = Input::from_str(&ctx, "hello", None); let input = Input::from_str(&ctx, "hello", None).unwrap();
assert_eq!(input.render(), "hello"); assert_eq!(input.render(), "hello");
} }
#[test] #[test]
fn input_with_agent_false_when_no_agent() { fn input_with_agent_false_when_no_agent() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let input = Input::from_str(&ctx, "test", None); let input = Input::from_str(&ctx, "test", None).unwrap();
assert!(!input.with_agent()); assert!(!input.with_agent());
} }
#[test] #[test]
fn input_session_returns_none_when_with_session_false() { fn input_session_returns_none_when_with_session_false() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let input = Input::from_str(&ctx, "test", Some(Role::new("r", "p"))); let input = Input::from_str(&ctx, "test", Some(Role::new("r", "p"))).unwrap();
let session = Some(Session::default()); let session = Some(Session::default());
assert!(input.session(&session).is_none()); assert!(input.session(&session).is_none());
} }
@@ -830,7 +830,7 @@ mod tests {
fn input_session_returns_some_when_with_session_true() { fn input_session_returns_some_when_with_session_true() {
let mut ctx = create_test_ctx(); let mut ctx = create_test_ctx();
ctx.session = Some(Session::default()); ctx.session = Some(Session::default());
let input = Input::from_str(&ctx, "test", None); let input = Input::from_str(&ctx, "test", None).unwrap();
let session = Some(Session::default()); let session = Some(Session::default());
assert!(input.session(&session).is_some()); assert!(input.session(&session).is_some());
} }
+1 -1
View File
@@ -29,7 +29,7 @@ pub async fn macro_execute(
let variables = macro_value let variables = macro_value
.resolve_variables(&new_args) .resolve_variables(&new_args)
.map_err(|err| anyhow!("{err}. Usage: {}", macro_value.usage(name)))?; .map_err(|err| anyhow!("{err}. Usage: {}", macro_value.usage(name)))?;
let role = ctx.extract_role(ctx.app.config.as_ref()); let role = ctx.extract_role(ctx.app.config.as_ref())?;
let mut app_config = (*ctx.app.config).clone(); let mut app_config = (*ctx.app.config).clone();
app_config.temperature = role.temperature(); app_config.temperature = role.temperature();
app_config.top_p = role.top_p(); app_config.top_p = role.top_p();
+22 -20
View File
@@ -37,6 +37,7 @@ use gman::providers::SupportedProvider;
use indexmap::IndexMap; use indexmap::IndexMap;
use indoc::formatdoc; use indoc::formatdoc;
use inquire::{Confirm, MultiSelect, Text, list_option::ListOption, validator::Validation}; use inquire::{Confirm, MultiSelect, Text, list_option::ListOption, validator::Validation};
use log::warn;
use parking_lot::RwLock; use parking_lot::RwLock;
use std::collections::{BTreeSet, HashMap, HashSet}; use std::collections::{BTreeSet, HashMap, HashSet};
use std::fs::{File, OpenOptions, read_dir, read_to_string, remove_dir_all, remove_file}; use std::fs::{File, OpenOptions, read_dir, read_to_string, remove_dir_all, remove_file};
@@ -601,7 +602,7 @@ impl RequestContext {
} }
} }
pub fn extract_role(&self, app: &AppConfig) -> Role { pub fn extract_role(&self, app: &AppConfig) -> Result<Role> {
let mut role = if let Some(session) = self.session.as_ref() { let mut role = if let Some(session) = self.session.as_ref() {
session.to_role() session.to_role()
} else if let Some(agent) = self.agent.as_ref() { } else if let Some(agent) = self.agent.as_ref() {
@@ -627,15 +628,13 @@ impl RequestContext {
} }
} }
match SkillPolicy::effective( let policy = SkillPolicy::effective(
app, app,
self.role.as_ref(), self.role.as_ref(),
self.agent.as_ref(), self.agent.as_ref(),
self.session.as_ref(), self.session.as_ref(),
) { )?;
Ok(policy) => self.skill_registry.effective_role(&role, &policy), Ok(self.skill_registry.effective_role(&role, &policy))
Err(_) => role,
}
} }
pub fn auto_continue_config(&self) -> AutoContinueConfig { pub fn auto_continue_config(&self) -> AutoContinueConfig {
@@ -852,7 +851,7 @@ impl RequestContext {
Some(rag) => rag.get_config(), Some(rag) => rag.get_config(),
None => (app.rag_reranker_model.clone(), app.rag_top_k), None => (app.rag_reranker_model.clone(), app.rag_top_k),
}; };
let role = self.extract_role(app); let role = self.extract_role(app)?;
let mut items = vec![ let mut items = vec![
("model", role.model().id()), ("model", role.model().id()),
( (
@@ -1025,7 +1024,10 @@ impl RequestContext {
pub fn generate_prompt_context(&self, app: &AppConfig) -> HashMap<&str, String> { pub fn generate_prompt_context(&self, app: &AppConfig) -> HashMap<&str, String> {
let mut output = HashMap::new(); let mut output = HashMap::new();
let role = self.extract_role(app); let role = self.extract_role(app).unwrap_or_else(|err| {
warn!("failed to compute effective role for prompt rendering: {err}");
Role::default()
});
output.insert("model", role.model().id()); output.insert("model", role.model().id());
output.insert("client_name", role.model().client_name().to_string()); output.insert("client_name", role.model().client_name().to_string());
output.insert("model_name", role.model().name().to_string()); output.insert("model_name", role.model().name().to_string());
@@ -2364,12 +2366,12 @@ impl RequestContext {
format!("Failed to cleanup previous '{TEMP_SESSION_NAME}' session") format!("Failed to cleanup previous '{TEMP_SESSION_NAME}' session")
})?; })?;
} }
session = Some(Session::new_from_ctx(self, app, TEMP_SESSION_NAME)); session = Some(Session::new_from_ctx(self, app, TEMP_SESSION_NAME)?);
} }
Some(name) => { Some(name) => {
let session_path = self.session_file(name); let session_path = self.session_file(name);
if !session_path.exists() { if !session_path.exists() {
session = Some(Session::new_from_ctx(self, app, name)); session = Some(Session::new_from_ctx(self, app, name)?);
} else { } else {
session = Some(Session::load_from_ctx(self, app, name, &session_path)?); session = Some(Session::load_from_ctx(self, app, name, &session_path)?);
} }
@@ -2788,7 +2790,7 @@ impl RequestContext {
.summarization_prompt .summarization_prompt
.clone() .clone()
.unwrap_or_else(|| SUMMARIZATION_PROMPT.into()); .unwrap_or_else(|| SUMMARIZATION_PROMPT.into());
let input = Input::from_str(self, &prompt, None); let input = Input::from_str(self, &prompt, None)?;
let summary = input.fetch_chat_text().await?; let summary = input.fetch_chat_text().await?;
let summary_context_prompt = self let summary_context_prompt = self
.app .app
@@ -2823,7 +2825,7 @@ impl RequestContext {
None => bail!("No chat history"), None => bail!("No chat history"),
}; };
let role = self.retrieve_role(app, CREATE_TITLE_ROLE)?; let role = self.retrieve_role(app, CREATE_TITLE_ROLE)?;
let input = Input::from_str(self, &text, Some(role)); let input = Input::from_str(self, &text, Some(role))?;
let text = input.fetch_chat_text().await?; let text = input.fetch_chat_text().await?;
if let Some(session) = self.session.as_mut() { if let Some(session) = self.session.as_mut() {
session.set_autoname(&text); session.set_autoname(&text);
@@ -3077,7 +3079,7 @@ mod tests {
let app = ctx.app.config.clone(); let app = ctx.app.config.clone();
let role = Role::new("myrole", "my prompt"); let role = Role::new("myrole", "my prompt");
ctx.use_role_obj(role).unwrap(); ctx.use_role_obj(role).unwrap();
let extracted = ctx.extract_role(&app); let extracted = ctx.extract_role(&app).unwrap();
assert_eq!(extracted.name(), "myrole"); assert_eq!(extracted.name(), "myrole");
} }
@@ -3085,7 +3087,7 @@ mod tests {
fn extract_role_returns_default_when_nothing_active() { fn extract_role_returns_default_when_nothing_active() {
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let app = ctx.app.config.clone(); let app = ctx.app.config.clone();
let extracted = ctx.extract_role(&app); let extracted = ctx.extract_role(&app).unwrap();
assert_eq!(extracted.name(), ""); assert_eq!(extracted.name(), "");
} }
@@ -3576,7 +3578,7 @@ mod tests {
#[test] #[test]
fn discontinuous_last_message_sets_continuous_false() { fn discontinuous_last_message_sets_continuous_false() {
let mut ctx = create_test_ctx(); let mut ctx = create_test_ctx();
let input = Input::from_str(&ctx, "test", None); let input = Input::from_str(&ctx, "test", None).unwrap();
ctx.last_message = Some(LastMessage::new(input, "reply".to_string())); ctx.last_message = Some(LastMessage::new(input, "reply".to_string()));
assert!(ctx.last_message.as_ref().unwrap().continuous); assert!(ctx.last_message.as_ref().unwrap().continuous);
ctx.discontinuous_last_message(); ctx.discontinuous_last_message();
@@ -3594,7 +3596,7 @@ mod tests {
#[test] #[test]
fn before_chat_completion_sets_last_message() { fn before_chat_completion_sets_last_message() {
let mut ctx = create_test_ctx(); let mut ctx = create_test_ctx();
let input = Input::from_str(&ctx, "hello", None); let input = Input::from_str(&ctx, "hello", None).unwrap();
ctx.before_chat_completion(&input).unwrap(); ctx.before_chat_completion(&input).unwrap();
assert!(ctx.last_message.is_some()); assert!(ctx.last_message.is_some());
let lm = ctx.last_message.as_ref().unwrap(); let lm = ctx.last_message.as_ref().unwrap();
@@ -3618,7 +3620,7 @@ mod tests {
ctx.skill_registry.insert(ephemeral).unwrap(); ctx.skill_registry.insert(ephemeral).unwrap();
ctx.skill_registry.insert(persistent).unwrap(); ctx.skill_registry.insert(persistent).unwrap();
let input = Input::from_str(&ctx, "hello", None); let input = Input::from_str(&ctx, "hello", None).unwrap();
let app = Arc::clone(&ctx.app.config); let app = Arc::clone(&ctx.app.config);
ctx.after_chat_completion(app.as_ref(), &input, "response", &[]) ctx.after_chat_completion(app.as_ref(), &input, "response", &[])
.unwrap(); .unwrap();
@@ -3641,7 +3643,7 @@ mod tests {
let ephemeral = Skill::new("ephemeral", "---\nauto_unload: true\n---\nbody"); let ephemeral = Skill::new("ephemeral", "---\nauto_unload: true\n---\nbody");
ctx.skill_registry.insert(ephemeral).unwrap(); ctx.skill_registry.insert(ephemeral).unwrap();
let input = Input::from_str(&ctx, "hello", None); let input = Input::from_str(&ctx, "hello", None).unwrap();
let app = Arc::clone(&ctx.app.config); let app = Arc::clone(&ctx.app.config);
let tool_result = let tool_result =
ToolResult::new(crate::function::ToolCall::default(), serde_json::json!({})); ToolResult::new(crate::function::ToolCall::default(), serde_json::json!({}));
@@ -3803,7 +3805,7 @@ mod tests {
fn session_new_from_ctx_captures_state() { fn session_new_from_ctx_captures_state() {
let _guard = TestConfigDirGuard::new(); let _guard = TestConfigDirGuard::new();
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let session = Session::new_from_ctx(&ctx, &ctx.app.config, "test-session"); let session = Session::new_from_ctx(&ctx, &ctx.app.config, "test-session").unwrap();
assert_eq!(session.name(), "test-session"); assert_eq!(session.name(), "test-session");
assert!(session.is_empty()); assert!(session.is_empty());
} }
@@ -3813,7 +3815,7 @@ mod tests {
fn session_save_creates_file() { fn session_save_creates_file() {
let _guard = TestConfigDirGuard::new(); let _guard = TestConfigDirGuard::new();
let ctx = create_test_ctx(); let ctx = create_test_ctx();
let mut session = Session::new_from_ctx(&ctx, &ctx.app.config, "save-test"); let mut session = Session::new_from_ctx(&ctx, &ctx.app.config, "save-test").unwrap();
let session_path = ctx.session_file("save-test"); let session_path = ctx.session_file("save-test");
ensure_parent_exists(&session_path).unwrap(); ensure_parent_exists(&session_path).unwrap();
+4 -4
View File
@@ -106,8 +106,8 @@ impl Session {
} }
} }
pub fn new_from_ctx(ctx: &RequestContext, app: &AppConfig, name: &str) -> Self { pub fn new_from_ctx(ctx: &RequestContext, app: &AppConfig, name: &str) -> Result<Self> {
let role = ctx.extract_role(app); let role = ctx.extract_role(app)?;
let mut session = Self { let mut session = Self {
name: name.to_string(), name: name.to_string(),
save_session: app.save_session, save_session: app.save_session,
@@ -115,7 +115,7 @@ impl Session {
}; };
session.set_role(role); session.set_role(role);
session.dirty = false; session.dirty = false;
session Ok(session)
} }
pub fn load_from_ctx( pub fn load_from_ctx(
@@ -817,7 +817,7 @@ mod tests {
functions: Functions::default(), functions: Functions::default(),
}); });
let ctx = RequestContext::new(app_state, WorkingMode::Cmd); let ctx = RequestContext::new(app_state, WorkingMode::Cmd);
let session = Session::new_from_ctx(&ctx, &app_config, "test-session"); let session = Session::new_from_ctx(&ctx, &app_config, "test-session").unwrap();
assert_eq!(session.name(), "test-session"); assert_eq!(session.name(), "test-session");
assert_eq!(session.save_session(), app_config.save_session); assert_eq!(session.save_session(), app_config.save_session);
+3 -3
View File
@@ -469,7 +469,7 @@ pub async fn run_agent_for_graph(
child_ctx.init_agent_shared_variables()?; child_ctx.init_agent_shared_variables()?;
} }
let input = Input::from_str(&child_ctx, prompt, None); let input = Input::from_str(&child_ctx, prompt, None)?;
debug!("Spawning agent '{agent_name}' for graph node as '{agent_id}'"); debug!("Spawning agent '{agent_name}' for graph node as '{agent_id}'");
@@ -635,7 +635,7 @@ async fn handle_spawn(ctx: &mut RequestContext, args: &Value) -> Result<Value> {
child_ctx.init_agent_shared_variables()?; child_ctx.init_agent_shared_variables()?;
} }
let input = Input::from_str(&child_ctx, &prompt, None); let input = Input::from_str(&child_ctx, &prompt, None)?;
debug!("Spawning child agent '{agent_name}' as '{agent_id}'"); debug!("Spawning child agent '{agent_name}' as '{agent_id}'");
@@ -1228,7 +1228,7 @@ async fn summarize_output(ctx: &RequestContext, agent_name: &str, output: &str)
"Summarize the following sub-agent output from '{}':\n\n{}", "Summarize the following sub-agent output from '{}':\n\n{}",
agent_name, output agent_name, output
); );
let input = Input::from_str(ctx, &user_message, Some(role)); let input = Input::from_str(ctx, &user_message, Some(role))?;
let summary = input.fetch_chat_text().await?; let summary = input.fetch_chat_text().await?;
+4 -6
View File
@@ -115,15 +115,13 @@ async fn run(
let saved_agent_skill_state = swap_in_node_skill_policy(node, parent_ctx); let saved_agent_skill_state = swap_in_node_skill_policy(node, parent_ctx);
let composed_role = match SkillPolicy::effective( let policy = SkillPolicy::effective(
&parent_ctx.app.config, &parent_ctx.app.config,
parent_ctx.role.as_ref(), parent_ctx.role.as_ref(),
parent_ctx.agent.as_ref(), parent_ctx.agent.as_ref(),
parent_ctx.session.as_ref(), parent_ctx.session.as_ref(),
) { )?;
Ok(policy) => parent_ctx.skill_registry.effective_role(&role, &policy), let composed_role = parent_ctx.skill_registry.effective_role(&role, &policy);
Err(_) => role,
};
let saved_role = parent_ctx.role.clone(); let saved_role = parent_ctx.role.clone();
parent_ctx.role = Some(composed_role); parent_ctx.role = Some(composed_role);
@@ -203,7 +201,7 @@ async fn run_chat_loop(node: &LlmNode, prompt: &str, ctx: &mut RequestContext) -
let abort = create_abort_signal(); let abort = create_abort_signal();
let app_cfg = Arc::clone(&ctx.app.config); let app_cfg = Arc::clone(&ctx.app.config);
let role_for_input = ctx.role.clone(); let role_for_input = ctx.role.clone();
let mut input = Input::from_str(ctx, prompt, role_for_input); let mut input = Input::from_str(ctx, prompt, role_for_input)?;
let mut accumulated = String::new(); let mut accumulated = String::new();
for turn in 0..node.max_iterations { for turn in 0..node.max_iterations {
+1 -1
View File
@@ -76,7 +76,7 @@ async fn run_one_shot(prompt: &str, ctx: &mut RequestContext) -> Result<String>
let abort = create_abort_signal(); let abort = create_abort_signal();
let app_cfg = Arc::clone(&ctx.app.config); let app_cfg = Arc::clone(&ctx.app.config);
let role_for_input = ctx.role.clone(); let role_for_input = ctx.role.clone();
let input = Input::from_str(ctx, prompt, role_for_input); let input = Input::from_str(ctx, prompt, role_for_input)?;
let client = input.create_client()?; let client = input.create_client()?;
ctx.before_chat_completion(&input)?; ctx.before_chat_completion(&input)?;
let (output, tool_results) = let (output, tool_results) =
+2 -2
View File
@@ -457,7 +457,7 @@ async fn shell_execute(
} }
'd' => { 'd' => {
let role = ctx.retrieve_role(app.as_ref(), EXPLAIN_SHELL_ROLE)?; let role = ctx.retrieve_role(app.as_ref(), EXPLAIN_SHELL_ROLE)?;
let input = Input::from_str(ctx, &eval_str, Some(role)); let input = Input::from_str(ctx, &eval_str, Some(role))?;
if input.stream() { if input.stream() {
call_chat_completions_streaming( call_chat_completions_streaming(
&input, &input,
@@ -502,7 +502,7 @@ async fn create_input(
) -> Result<Input> { ) -> Result<Input> {
let text = text.unwrap_or_default(); let text = text.unwrap_or_default();
let input = if file.is_empty() { let input = if file.is_empty() {
Input::from_str(ctx, &text, None) Input::from_str(ctx, &text, None)?
} else { } else {
Input::from_files_with_spinner(ctx, &text, file.to_vec(), None, abort_signal).await? Input::from_files_with_spinner(ctx, &text, file.to_vec(), None, abort_signal).await?
}; };
+6 -6
View File
@@ -503,7 +503,7 @@ pub async fn run_repl_command(
Some((name, text)) => { Some((name, text)) => {
let app = Arc::clone(&ctx.app.config); let app = Arc::clone(&ctx.app.config);
let role = ctx.retrieve_role(app.as_ref(), name.trim())?; let role = ctx.retrieve_role(app.as_ref(), name.trim())?;
let input = Input::from_str(ctx, text, Some(role)); let input = Input::from_str(ctx, text, Some(role))?;
ask(ctx, abort_signal.clone(), input, false).await?; ask(ctx, abort_signal.clone(), input, false).await?;
} }
None => { None => {
@@ -654,7 +654,7 @@ pub async fn run_repl_command(
match text { match text {
Some(text) => { Some(text) => {
println!("{}", dimmed_text(&format!(">> {text}"))); println!("{}", dimmed_text(&format!(">> {text}")));
let input = Input::from_str(ctx, &text, None); let input = Input::from_str(ctx, &text, None)?;
ask(ctx, abort_signal.clone(), input, true).await?; ask(ctx, abort_signal.clone(), input, true).await?;
} }
None => { None => {
@@ -822,7 +822,7 @@ pub async fn run_repl_command(
None => bail!("Unable to regenerate the response"), None => bail!("Unable to regenerate the response"),
}; };
let app = Arc::clone(&ctx.app.config); let app = Arc::clone(&ctx.app.config);
input.set_regenerate(ctx.extract_role(&app)); input.set_regenerate(ctx.extract_role(&app)?);
ask(ctx, abort_signal.clone(), input, true).await?; ask(ctx, abort_signal.clone(), input, true).await?;
} }
".set" => match args { ".set" => match args {
@@ -944,7 +944,7 @@ pub async fn run_repl_command(
}, },
None => { None => {
reset_continuation(ctx); reset_continuation(ctx);
let input = Input::from_str(ctx, line, None); let input = Input::from_str(ctx, line, None)?;
ask(ctx, abort_signal.clone(), input, true).await?; ask(ctx, abort_signal.clone(), input, true).await?;
} }
} }
@@ -1040,7 +1040,7 @@ async fn ask(
format!("{prompt}\n\n{todo_state}") format!("{prompt}\n\n{todo_state}")
}; };
let continuation_input = Input::from_str(ctx, &full_prompt, None); let continuation_input = Input::from_str(ctx, &full_prompt, None)?;
ask(ctx, abort_signal, continuation_input, false).await ask(ctx, abort_signal, continuation_input, false).await
} else { } else {
reset_continuation(ctx); reset_continuation(ctx);
@@ -1113,7 +1113,7 @@ async fn ask(
format!("{prompt}\n\n{todo_state}") format!("{prompt}\n\n{todo_state}")
}; };
let continuation_input = Input::from_str(ctx, &full_prompt, None); let continuation_input = Input::from_str(ctx, &full_prompt, None)?;
return ask(ctx, abort_signal, continuation_input, false).await; return ask(ctx, abort_signal, continuation_input, false).await;
} }
} }