Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 7 additions & 23 deletions crates/jcode-agent-runtime/src/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,19 +174,15 @@ fn default_version() -> String {
/// invariants. Displayed to users when a TOML file fails to load.
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
pub enum DefinitionError {
#[error(
"agent id `{0}` is invalid: must be non-empty, lowercase ASCII alphanumeric or hyphen"
)]
#[error("agent id `{0}` is invalid: must be non-empty, lowercase ASCII alphanumeric or hyphen")]
InvalidId(String),

#[error(
"agent `{id}` has both `inherit_parent_system_prompt = true` and a non-empty `system_prompt`. Set one or the other."
)]
SystemPromptConflict { id: String },

#[error(
"agent `{id}` has `output_mode = structured_output` but `output_schema` is missing"
)]
#[error("agent `{id}` has `output_mode = structured_output` but `output_schema` is missing")]
StructuredOutputMissingSchema { id: String },

#[error("agent `{id}` references itself in `spawnable_agents`")]
Expand All @@ -209,9 +205,7 @@ pub enum DefinitionError {
/// agent spawn time.
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
pub enum ReferenceError {
#[error(
"agent `{id}` references unknown tool(s): {unknown}. Available tools: {available}"
)]
#[error("agent `{id}` references unknown tool(s): {unknown}. Available tools: {available}")]
UnknownTools {
id: String,
unknown: String,
Expand Down Expand Up @@ -245,8 +239,7 @@ impl AgentDefinition {
}

// 3. structured_output requires schema
if matches!(self.output_mode, OutputMode::StructuredOutput)
&& self.output_schema.is_none()
if matches!(self.output_mode, OutputMode::StructuredOutput) && self.output_schema.is_none()
{
return Err(DefinitionError::StructuredOutputMissingSchema {
id: self.id.clone(),
Expand Down Expand Up @@ -422,30 +415,21 @@ mod tests {
fn id_validation_rejects_uppercase() {
let mut d = minimal_definition("File-Picker");
d.id = "File-Picker".to_string();
assert!(matches!(
d.validate(),
Err(DefinitionError::InvalidId(_))
));
assert!(matches!(d.validate(), Err(DefinitionError::InvalidId(_))));
}

#[test]
fn id_validation_rejects_underscore() {
let mut d = minimal_definition("file_picker");
d.id = "file_picker".to_string();
assert!(matches!(
d.validate(),
Err(DefinitionError::InvalidId(_))
));
assert!(matches!(d.validate(), Err(DefinitionError::InvalidId(_))));
}

#[test]
fn id_validation_rejects_leading_hyphen() {
let mut d = minimal_definition("ok");
d.id = "-bad".to_string();
assert!(matches!(
d.validate(),
Err(DefinitionError::InvalidId(_))
));
assert!(matches!(d.validate(), Err(DefinitionError::InvalidId(_))));
}

#[test]
Expand Down
6 changes: 2 additions & 4 deletions crates/jcode-agent-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@ pub use signals::{
};

// New public surface (Phase 0).
pub use definition::{
AgentDefinition, DefinitionError, ReferenceError, DEFAULT_AGENT_VERSION,
};
pub use definition::{AgentDefinition, DEFAULT_AGENT_VERSION, DefinitionError, ReferenceError};
pub use output::OutputMode;
pub use reasoning::ReasoningEffort;
pub use registry::{AgentRegistry, AgentSource, LoadError, LoadedAgent, SourceKind};
pub use tier::{resolve_model, resolve_model_with_source, ModelTier, ResolutionSource};
pub use tier::{ModelTier, ResolutionSource, resolve_model, resolve_model_with_source};
5 changes: 4 additions & 1 deletion crates/jcode-agent-runtime/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ mod tests {

#[test]
fn parse_accepts_aliases() {
assert_eq!(OutputMode::parse("last_message"), Some(OutputMode::LastMessage));
assert_eq!(
OutputMode::parse("last_message"),
Some(OutputMode::LastMessage)
);
assert_eq!(OutputMode::parse("all"), Some(OutputMode::AllMessages));
assert_eq!(
OutputMode::parse("structured"),
Expand Down
10 changes: 8 additions & 2 deletions crates/jcode-agent-runtime/src/reasoning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,15 @@ mod tests {
ReasoningEffort::parse("minimal"),
Some(ReasoningEffort::Minimal)
);
assert_eq!(ReasoningEffort::parse("OFF"), Some(ReasoningEffort::Minimal));
assert_eq!(
ReasoningEffort::parse("OFF"),
Some(ReasoningEffort::Minimal)
);
assert_eq!(ReasoningEffort::parse("max"), Some(ReasoningEffort::High));
assert_eq!(ReasoningEffort::parse("default"), Some(ReasoningEffort::Medium));
assert_eq!(
ReasoningEffort::parse("default"),
Some(ReasoningEffort::Medium)
);
assert_eq!(ReasoningEffort::parse(""), None);
assert_eq!(ReasoningEffort::parse("absurd"), None);
}
Expand Down
57 changes: 30 additions & 27 deletions crates/jcode-agent-runtime/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,7 @@ pub enum LoadError {
source: DefinitionError,
},

#[error(
"filename `{path}` does not match agent id `{id}`. Rename the file to `{id}.toml`."
)]
#[error("filename `{path}` does not match agent id `{id}`. Rename the file to `{id}.toml`.")]
FileNameMismatch { path: PathBuf, id: String },
}

Expand Down Expand Up @@ -175,10 +173,7 @@ impl AgentRegistry {

/// Register a builtin agent. Builtins have the lowest priority and
/// are overridable by both user and project files of the same id.
pub fn register_builtin(
&mut self,
definition: AgentDefinition,
) -> Result<(), DefinitionError> {
pub fn register_builtin(&mut self, definition: AgentDefinition) -> Result<(), DefinitionError> {
definition.validate()?;
self.insert(LoadedAgent {
definition,
Expand Down Expand Up @@ -233,10 +228,7 @@ impl AgentRegistry {
AgentSource::ProjectLocal { path: path.clone() }
}
};
self.insert(LoadedAgent {
definition,
source,
});
self.insert(LoadedAgent { definition, source });
loaded += 1;
}
Err(err) => {
Expand Down Expand Up @@ -333,7 +325,10 @@ mod tests {
fn missing_dir_is_zero_load_not_error() {
let mut reg = AgentRegistry::new();
let n = reg
.load_directory(Path::new("/nonexistent/jcode-test-dir"), SourceKind::UserGlobal)
.load_directory(
Path::new("/nonexistent/jcode-test-dir"),
SourceKind::UserGlobal,
)
.unwrap();
assert_eq!(n, 0);
assert!(reg.is_empty());
Expand Down Expand Up @@ -382,7 +377,10 @@ mod tests {
output_schema: None,
};
reg.register_builtin(builtin_def.clone()).unwrap();
assert_eq!(reg.get("editor").unwrap().definition.display_name, "Builtin Editor");
assert_eq!(
reg.get("editor").unwrap().definition.display_name,
"Builtin Editor"
);

// User
let user_dir = temp_dir("user");
Expand All @@ -394,8 +392,12 @@ mod tests {
display_name = "User Editor"
"#,
);
reg.load_directory(&user_dir, SourceKind::UserGlobal).unwrap();
assert_eq!(reg.get("editor").unwrap().definition.display_name, "User Editor");
reg.load_directory(&user_dir, SourceKind::UserGlobal)
.unwrap();
assert_eq!(
reg.get("editor").unwrap().definition.display_name,
"User Editor"
);

// Project
let proj_dir = temp_dir("proj");
Expand All @@ -407,7 +409,8 @@ mod tests {
display_name = "Project Editor"
"#,
);
reg.load_directory(&proj_dir, SourceKind::ProjectLocal).unwrap();
reg.load_directory(&proj_dir, SourceKind::ProjectLocal)
.unwrap();
assert_eq!(
reg.get("editor").unwrap().definition.display_name,
"Project Editor"
Expand All @@ -432,10 +435,7 @@ mod tests {
reg.load_directory(&dir, SourceKind::UserGlobal).unwrap();
assert!(reg.is_empty(), "no agents registered");
assert_eq!(reg.load_errors().len(), 1);
assert!(matches!(
reg.load_errors()[0],
LoadError::Parse { .. }
));
assert!(matches!(reg.load_errors()[0], LoadError::Parse { .. }));
}

#[test]
Expand All @@ -453,10 +453,7 @@ mod tests {
reg.load_directory(&dir, SourceKind::UserGlobal).unwrap();
assert!(reg.is_empty());
assert_eq!(reg.load_errors().len(), 1);
assert!(matches!(
reg.load_errors()[0],
LoadError::Invalid { .. }
));
assert!(matches!(reg.load_errors()[0], LoadError::Invalid { .. }));
}

#[test]
Expand Down Expand Up @@ -506,14 +503,20 @@ mod tests {
write_toml(
&dir,
&format!("{id}.toml"),
&format!(r#"id = "{id}"
&format!(
r#"id = "{id}"
display_name = "{id}"
"#),
"#
),
);
}
let mut reg = AgentRegistry::new();
reg.load_directory(&dir, SourceKind::UserGlobal).unwrap();
let ids: Vec<_> = reg.iter_sorted().iter().map(|a| a.definition.id.clone()).collect();
let ids: Vec<_> = reg
.iter_sorted()
.iter()
.map(|a| a.definition.id.clone())
.collect();
assert_eq!(ids, vec!["alpha", "mid", "zeta"]);
}

Expand Down
10 changes: 2 additions & 8 deletions crates/jcode-agent-runtime/src/tier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,10 @@ pub enum ResolutionSource {
/// Used `agent.model_override` directly.
Override(String),
/// Used the env var backing `tier`.
Tier {
tier: ModelTier,
model: String,
},
Tier { tier: ModelTier, model: String },
/// Tier was preferred but the env var was unset, so fell back to the
/// session's current model.
TierFallback {
tier: ModelTier,
model: String,
},
TierFallback { tier: ModelTier, model: String },
/// No override or tier preference; using the session's current model.
SessionDefault(String),
}
Expand Down
22 changes: 16 additions & 6 deletions crates/jcode-agent-runtime/tests/sample_agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@

use std::path::PathBuf;

use jcode_agent_runtime::{
AgentRegistry, ModelTier, OutputMode, ReasoningEffort, SourceKind,
};
use jcode_agent_runtime::{AgentRegistry, ModelTier, OutputMode, ReasoningEffort, SourceKind};

/// Path to the project-root sample agents directory, relative to the
/// crate manifest. Deliberately constructed via `CARGO_MANIFEST_DIR` so
Expand All @@ -20,7 +18,12 @@ use jcode_agent_runtime::{
fn samples_dir() -> PathBuf {
let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
// crates/jcode-agent-runtime → ../../ .jcode/agents
crate_dir.parent().unwrap().parent().unwrap().join(".jcode/agents")
crate_dir
.parent()
.unwrap()
.parent()
.unwrap()
.join(".jcode/agents")
}

#[test]
Expand All @@ -37,7 +40,11 @@ fn loads_bundled_sample_agents() {
.load_directory(&dir, SourceKind::ProjectLocal)
.expect("load_directory");
assert!(n >= 2, "expected at least 2 sample agents, got {n}");
assert!(reg.load_errors().is_empty(), "load errors: {:?}", reg.load_errors());
assert!(
reg.load_errors().is_empty(),
"load errors: {:?}",
reg.load_errors()
);
}

#[test]
Expand All @@ -56,7 +63,10 @@ fn file_picker_sample_has_expected_shape() {
assert_eq!(agent.display_name, "Fletcher the File Fetcher");
assert_eq!(agent.prefer_tier, Some(ModelTier::Routine));
assert_eq!(agent.reasoning, Some(ReasoningEffort::Minimal));
assert!(!agent.include_message_history, "file picker uses clean slate");
assert!(
!agent.include_message_history,
"file picker uses clean slate"
);
assert!(!agent.inherit_parent_system_prompt);
assert_eq!(agent.output_mode, OutputMode::LastMessage);
assert!(agent.tool_names.iter().any(|t| t == "read"));
Expand Down
9 changes: 6 additions & 3 deletions evals/jbench/src/agent_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,12 @@ pub async fn run_agent_in_repo(config: AgentRunConfig) -> Result<EvalRun> {
.current_dir(&config.repo_path)
.envs(&env_vars)
.args([
"agent", "run",
"--agent", &config.agent_id,
"--output-mode", "stream",
"agent",
"run",
"--agent",
&config.agent_id,
"--output-mode",
"stream",
"--no-interactive",
])
.stdin(Stdio::piped())
Expand Down
Loading