Skip to content

Commit ddf2619

Browse files
committed
refactor: sort collections and improve determinism
- Sort paths and directories in files_in_workspace for consistent trie building - Sort context paths in pp_utils for stable processing order - Replace HashMap with BTreeMap in pp_tool_results for sorted iteration - Sort customization modes by title/id to ensure consistent UI order - Enhance model pattern matching with canonical names and wildcard support - Change default reasoning effort to Medium when boost_reasoning enabled - Update anthropic test comment for clarity
1 parent 18a7201 commit ddf2619

File tree

7 files changed

+98
-21
lines changed

7 files changed

+98
-21
lines changed

refact-agent/engine/src/chat/prepare.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ fn adapt_sampling_for_reasoning_models(
324324
&& model_record.supports_boost_reasoning
325325
&& sampling_parameters.boost_reasoning
326326
{
327-
sampling_parameters.reasoning_effort = Some(ReasoningEffort::High);
327+
sampling_parameters.reasoning_effort = Some(ReasoningEffort::Medium);
328328
}
329329
sampling_parameters.thinking = None;
330330
sampling_parameters.enable_thinking = None;

refact-agent/engine/src/files_in_workspace.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -165,20 +165,22 @@ impl CacheCorrection {
165165
}
166166

167167
pub fn build(paths: &Vec<PathBuf>, workspace_folders: &Vec<PathBuf>) -> CacheCorrection {
168-
let filenames = PathTrie::build(&paths, &workspace_folders);
169-
// TODO: I'm not sure how directories should be collected
170-
let directories: Vec<PathBuf> = {
168+
let mut sorted_paths = paths.clone();
169+
sorted_paths.sort();
170+
171+
let filenames = PathTrie::build(&sorted_paths, &workspace_folders);
172+
173+
let mut directories: Vec<PathBuf> = {
171174
let mut unique_directories = HashSet::new();
172-
for p in paths.iter() {
175+
for p in sorted_paths.iter() {
173176
if let Some(parent) = p.parent() {
174177
unique_directories.insert(parent);
175178
}
176179
}
177-
unique_directories
178-
.iter()
179-
.map(|p| PathBuf::from(p))
180-
.collect()
180+
unique_directories.iter().map(|p| PathBuf::from(p)).collect()
181181
};
182+
directories.sort();
183+
182184
let directories = PathTrie::build(&directories, &workspace_folders);
183185
CacheCorrection {
184186
filenames,

refact-agent/engine/src/http/routers/v1/customization_editor.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use crate::files_correction::get_project_dirs;
1111
use crate::global_context::GlobalContext;
1212
use crate::yaml_configs::customization_registry::{load_merged_registry, load_registry_from_dir, invalidate_all_registry_caches, ConfigScope};
1313
use crate::yaml_configs::customization_types::*;
14+
use crate::yaml_configs::project_configs_bootstrap::{global_configs_try_create_all, project_configs_ensure_dirs};
15+
1416

1517
fn json_error(status: StatusCode, msg: &str) -> Result<Response<Body>, ScratchError> {
1618
let body = serde_json::json!({"error": msg});
@@ -81,9 +83,13 @@ pub async fn handle_v1_customization_registry(
8183
let dirs = get_project_dirs(gcx.clone()).await;
8284
let project_root = dirs.first().cloned();
8385

86+
let _ = global_configs_try_create_all(&config_dir).await;
87+
if let Some(ref root) = project_root {
88+
let _ = project_configs_ensure_dirs(root).await;
89+
}
90+
8491
let registry = load_merged_registry(&config_dir, project_root.as_deref()).await;
8592
let _global_registry = load_registry_from_dir(&config_dir).await;
86-
8793
let local_refact_dir = project_root.as_ref().map(|p| p.join(".refact"));
8894

8995
let make_config_item = |id: &str, kind: &str, title: &str, specific: bool| -> ConfigItem {
@@ -111,9 +117,31 @@ pub async fn handle_v1_customization_registry(
111117
}
112118
};
113119

114-
let mut modes: Vec<_> = registry.modes.values().map(|m| {
115-
make_config_item(&m.id, "modes", if m.title.is_empty() { &m.id } else { &m.title }, m.specific)
116-
}).collect();
120+
let mut modes: Vec<_> = Vec::new();
121+
let mut seen_mode_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
122+
123+
for m in registry.modes.values() {
124+
if seen_mode_ids.insert(m.id.clone()) {
125+
modes.push(make_config_item(
126+
&m.id,
127+
"modes",
128+
if m.title.is_empty() { &m.id } else { &m.title },
129+
m.specific,
130+
));
131+
}
132+
}
133+
134+
for m in &registry.mode_overrides {
135+
if seen_mode_ids.insert(m.id.clone()) {
136+
modes.push(make_config_item(
137+
&m.id,
138+
"modes",
139+
if m.title.is_empty() { &m.id } else { &m.title },
140+
m.specific,
141+
));
142+
}
143+
}
144+
117145
modes.sort_by(|a, b| a.title.cmp(&b.title).then_with(|| a.id.cmp(&b.id)));
118146

119147
let mut subagents: Vec<_> = registry.subagents.values().map(|s| {

refact-agent/engine/src/llm/adapters/anthropic.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -981,13 +981,13 @@ mod tests {
981981
"claude".to_string(),
982982
vec![ChatMessage::new("user".to_string(), "test".to_string())],
983983
);
984-
req_low_max.params.max_tokens = 4096; // Less than DEFAULT_THINKING_BUDGET (10000)
984+
req_low_max.params.max_tokens = 4096; // Less than DEFAULT_THINKING_BUDGET
985985
req_low_max.reasoning = ReasoningIntent::High; // Will use DEFAULT_THINKING_BUDGET
986986
req_low_max.stream = true;
987987

988988
let http = adapter.build_http(&req_low_max, &settings()).unwrap();
989-
// Should be adjusted: 10000 + max(4096, 1024) = 14096
990-
assert_eq!(http.body["max_tokens"], 14096);
989+
// Should be adjusted: budget + max(current_max, 1024)
990+
assert_eq!(http.body["max_tokens"], DEFAULT_THINKING_BUDGET + 4096);
991991
assert_eq!(http.body["thinking"]["budget_tokens"], DEFAULT_THINKING_BUDGET);
992992

993993
// Test with max_tokens > thinking budget (should NOT be adjusted)

refact-agent/engine/src/postprocessing/pp_tool_results.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use std::collections::HashMap;
1+
use std::collections::BTreeMap;
2+
23
use std::path::PathBuf;
34
use std::sync::Arc;
45
use tokenizers::Tokenizer;
@@ -104,7 +105,7 @@ fn deduplicate_and_merge_context_files(
104105
context_files: Vec<ContextFile>,
105106
existing_messages: &[ChatMessage],
106107
) -> (Vec<ContextFile>, Vec<String>) {
107-
let mut file_groups: HashMap<String, Vec<ContextFile>> = HashMap::new();
108+
let mut file_groups: BTreeMap<String, Vec<ContextFile>> = BTreeMap::new();
108109

109110
for cf in context_files {
110111
let canonical = canonical_path(&cf.file_name).to_string_lossy().to_string();

refact-agent/engine/src/postprocessing/pp_utils.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,9 @@ pub async fn pp_resolve_ctx_file_paths(
148148
}
149149
unique_cpaths.insert(context_file.file_name.clone());
150150
}
151+
let mut unique_cpaths_vec: Vec<String> = unique_cpaths.into_iter().collect();
152+
unique_cpaths_vec.sort();
151153

152-
let unique_cpaths_vec: Vec<String> = unique_cpaths.into_iter().collect();
153154
let shortified_vec: Vec<String> = shortify_paths(gcx.clone(), &unique_cpaths_vec).await;
154155
unique_cpaths_vec
155156
.into_iter()

refact-agent/engine/src/yaml_configs/customization_registry.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,14 +362,54 @@ pub fn resolve_subagent_for_model(
362362
}
363363

364364
fn model_matches_pattern(model_id: &str, pattern: &str) -> bool {
365+
let canonical = crate::caps::model_caps::canonicalize_model_name(model_id);
366+
let candidates = [
367+
canonical.original.as_str(),
368+
canonical.provider_stripped.as_str(),
369+
canonical.base_model.as_str(),
370+
canonical.last_segment.as_str(),
371+
canonical.last_segment_base.as_str(),
372+
];
373+
374+
candidates.iter().any(|c| model_matches_pattern_single(c, pattern))
375+
|| {
376+
let pattern_norm = normalize_model_match_str(pattern);
377+
candidates
378+
.iter()
379+
.any(|c| model_matches_pattern_single(&normalize_model_match_str(c), &pattern_norm))
380+
}
381+
}
382+
383+
fn normalize_model_match_str(s: &str) -> String {
384+
s.to_lowercase().replace('.', "-")
385+
}
386+
387+
fn model_matches_pattern_single(model_id: &str, pattern: &str) -> bool {
365388
if pattern == "*" {
366389
return true;
367390
}
368-
if pattern.ends_with("*") {
391+
392+
if !pattern.contains('*') {
393+
return model_id == pattern;
394+
}
395+
396+
if pattern.ends_with('*') {
369397
let prefix = &pattern[..pattern.len() - 1];
370398
return model_id.starts_with(prefix);
371399
}
372-
model_id == pattern
400+
401+
if pattern.starts_with('*') {
402+
let suffix = &pattern[1..];
403+
return model_id.ends_with(suffix);
404+
}
405+
406+
if let Some(star_pos) = pattern.find('*') {
407+
let prefix = &pattern[..star_pos];
408+
let suffix = &pattern[star_pos + 1..];
409+
return model_id.starts_with(prefix) && model_id.ends_with(suffix);
410+
}
411+
412+
false
373413
}
374414

375415
pub fn match_tool_confirm_action(rules: &[ToolConfirmRule], tool_name: &str) -> Option<String> {
@@ -504,6 +544,11 @@ mod tests {
504544

505545
#[test]
506546
fn test_model_matches_pattern_prefix() {
547+
assert!(model_matches_pattern("openai/gpt-4o", "gpt-4*"));
548+
assert!(model_matches_pattern("openrouter/openai/gpt-4o", "gpt-4*"));
549+
assert!(model_matches_pattern("claude-3.7-sonnet", "claude-3-7*"));
550+
assert!(model_matches_pattern("anthropic/claude-3.7-sonnet", "claude-3-7*"));
551+
507552
assert!(model_matches_pattern("gpt-4o", "gpt-*"));
508553
assert!(model_matches_pattern("gpt-4-turbo", "gpt-*"));
509554
assert!(!model_matches_pattern("claude-3", "gpt-*"));

0 commit comments

Comments
 (0)