Skip to content

Commit a54bb1c

Browse files
committed
batch fixes
1 parent 6f3c4e7 commit a54bb1c

File tree

12 files changed

+148
-79
lines changed

12 files changed

+148
-79
lines changed

Cargo.lock

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ members = [
1818
]
1919

2020
[workspace.package]
21-
version = "0.2.7"
21+
version = "0.2.8"
2222
edition = "2021"
2323
license = "Apache-2.0 OR MIT"
2424
repository = "https://github.com/RightNow-AI/openfang"

crates/openfang-channels/src/discord.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub struct DiscordAdapter {
3838
/// SECURITY: Bot token is zeroized on drop to prevent memory disclosure.
3939
token: Zeroizing<String>,
4040
client: reqwest::Client,
41-
allowed_guilds: Vec<u64>,
41+
allowed_guilds: Vec<String>,
4242
intents: u64,
4343
shutdown_tx: Arc<watch::Sender<bool>>,
4444
shutdown_rx: watch::Receiver<bool>,
@@ -51,7 +51,7 @@ pub struct DiscordAdapter {
5151
}
5252

5353
impl DiscordAdapter {
54-
pub fn new(token: String, allowed_guilds: Vec<u64>, intents: u64) -> Self {
54+
pub fn new(token: String, allowed_guilds: Vec<String>, intents: u64) -> Self {
5555
let (shutdown_tx, shutdown_rx) = watch::channel(false);
5656
Self {
5757
token: Zeroizing::new(token),
@@ -422,7 +422,7 @@ impl ChannelAdapter for DiscordAdapter {
422422
async fn parse_discord_message(
423423
d: &serde_json::Value,
424424
bot_user_id: &Arc<RwLock<Option<String>>>,
425-
allowed_guilds: &[u64],
425+
allowed_guilds: &[String],
426426
) -> Option<ChannelMessage> {
427427
let author = d.get("author")?;
428428
let author_id = author["id"].as_str()?;
@@ -442,8 +442,7 @@ async fn parse_discord_message(
442442
// Filter by allowed guilds
443443
if !allowed_guilds.is_empty() {
444444
if let Some(guild_id) = d["guild_id"].as_str() {
445-
let gid: u64 = guild_id.parse().unwrap_or(0);
446-
if !allowed_guilds.contains(&gid) {
445+
if !allowed_guilds.iter().any(|g| g == guild_id) {
447446
return None;
448447
}
449448
}
@@ -587,11 +586,11 @@ mod tests {
587586
});
588587

589588
// Not in allowed guilds
590-
let msg = parse_discord_message(&d, &bot_id, &[111, 222]).await;
589+
let msg = parse_discord_message(&d, &bot_id, &["111".into(), "222".into()]).await;
591590
assert!(msg.is_none());
592591

593592
// In allowed guilds
594-
let msg = parse_discord_message(&d, &bot_id, &[999]).await;
593+
let msg = parse_discord_message(&d, &bot_id, &["999".into()]).await;
595594
assert!(msg.is_some());
596595
}
597596

@@ -685,7 +684,7 @@ mod tests {
685684

686685
#[test]
687686
fn test_discord_adapter_creation() {
688-
let adapter = DiscordAdapter::new("test-token".to_string(), vec![123, 456], 33280);
687+
let adapter = DiscordAdapter::new("test-token".to_string(), vec!["123".to_string(), "456".to_string()], 33280);
689688
assert_eq!(adapter.name(), "discord");
690689
assert_eq!(adapter.channel_type(), ChannelType::Discord);
691690
}

crates/openfang-kernel/src/kernel.rs

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -924,14 +924,20 @@ impl OpenFangKernel {
924924
&& restored_entry.manifest.model.base_url.is_none()
925925
{
926926
let dm = &kernel.config.default_model;
927-
if !dm.provider.is_empty() {
928-
restored_entry.manifest.model.provider = dm.provider.clone();
929-
}
930-
if !dm.model.is_empty() {
931-
restored_entry.manifest.model.model = dm.model.clone();
932-
}
933-
if dm.base_url.is_some() {
934-
restored_entry.manifest.model.base_url = dm.base_url.clone();
927+
let is_default_provider = restored_entry.manifest.model.provider.is_empty()
928+
|| restored_entry.manifest.model.provider == "anthropic";
929+
let is_default_model = restored_entry.manifest.model.model.is_empty()
930+
|| restored_entry.manifest.model.model == "claude-sonnet-4-20250514";
931+
if is_default_provider && is_default_model {
932+
if !dm.provider.is_empty() {
933+
restored_entry.manifest.model.provider = dm.provider.clone();
934+
}
935+
if !dm.model.is_empty() {
936+
restored_entry.manifest.model.model = dm.model.clone();
937+
}
938+
if dm.base_url.is_some() {
939+
restored_entry.manifest.model.base_url = dm.base_url.clone();
940+
}
935941
}
936942
}
937943

@@ -998,18 +1004,26 @@ impl OpenFangKernel {
9981004
}
9991005
info!(agent = %name, id = %agent_id, exec_mode = ?manifest.exec_policy.as_ref().map(|p| &p.mode), "Agent exec_policy resolved");
10001006

1001-
// Overlay kernel default_model onto agent if no custom key/url is set.
1002-
// This ensures agents respect the user's configured provider from `openfang init`.
1007+
// Overlay kernel default_model onto agent if agent didn't explicitly choose.
1008+
// Only override provider/model when the agent uses the generic defaults
1009+
// (empty or the compile-time default "anthropic"/"claude-sonnet-4-20250514").
1010+
// This preserves explicit model choices like provider="groq", model="llama-3.3-70b".
10031011
if manifest.model.api_key_env.is_none() && manifest.model.base_url.is_none() {
10041012
let dm = &self.config.default_model;
1005-
if !dm.provider.is_empty() {
1006-
manifest.model.provider = dm.provider.clone();
1007-
}
1008-
if !dm.model.is_empty() {
1009-
manifest.model.model = dm.model.clone();
1010-
}
1011-
if dm.base_url.is_some() {
1012-
manifest.model.base_url = dm.base_url.clone();
1013+
let is_default_provider = manifest.model.provider.is_empty()
1014+
|| manifest.model.provider == "anthropic";
1015+
let is_default_model = manifest.model.model.is_empty()
1016+
|| manifest.model.model == "claude-sonnet-4-20250514";
1017+
if is_default_provider && is_default_model {
1018+
if !dm.provider.is_empty() {
1019+
manifest.model.provider = dm.provider.clone();
1020+
}
1021+
if !dm.model.is_empty() {
1022+
manifest.model.model = dm.model.clone();
1023+
}
1024+
if dm.base_url.is_some() {
1025+
manifest.model.base_url = dm.base_url.clone();
1026+
}
10131027
}
10141028
}
10151029

@@ -1448,6 +1462,7 @@ impl OpenFangKernel {
14481462
None
14491463
},
14501464
peer_agents,
1465+
current_date: Some(chrono::Local::now().format("%A, %B %d, %Y (%Y-%m-%d %H:%M %Z)").to_string()),
14511466
};
14521467
manifest.model.system_prompt =
14531468
openfang_runtime::prompt_builder::build_system_prompt(&prompt_ctx);
@@ -1916,6 +1931,7 @@ impl OpenFangKernel {
19161931
None
19171932
},
19181933
peer_agents,
1934+
current_date: Some(chrono::Local::now().format("%A, %B %d, %Y (%Y-%m-%d %H:%M %Z)").to_string()),
19191935
};
19201936
manifest.model.system_prompt =
19211937
openfang_runtime::prompt_builder::build_system_prompt(&prompt_ctx);
@@ -3682,7 +3698,9 @@ impl OpenFangKernel {
36823698

36833699
// If fallback models are configured, wrap in FallbackDriver
36843700
if !manifest.fallback_models.is_empty() {
3685-
let mut chain = vec![primary.clone()];
3701+
// Primary driver uses the agent's own model name (already set in request)
3702+
let mut chain: Vec<(std::sync::Arc<dyn openfang_runtime::llm_driver::LlmDriver>, String)> =
3703+
vec![(primary.clone(), String::new())];
36863704
for fb in &manifest.fallback_models {
36873705
let config = DriverConfig {
36883706
provider: fb.provider.clone(),
@@ -3693,15 +3711,15 @@ impl OpenFangKernel {
36933711
base_url: fb.base_url.clone(),
36943712
};
36953713
match drivers::create_driver(&config) {
3696-
Ok(d) => chain.push(d),
3714+
Ok(d) => chain.push((d, fb.model.clone())),
36973715
Err(e) => {
36983716
warn!("Fallback driver '{}' failed to init: {e}", fb.provider);
36993717
}
37003718
}
37013719
}
37023720
if chain.len() > 1 {
37033721
return Ok(Arc::new(
3704-
openfang_runtime::drivers::fallback::FallbackDriver::new(chain),
3722+
openfang_runtime::drivers::fallback::FallbackDriver::with_models(chain),
37053723
));
37063724
}
37073725
}

crates/openfang-runtime/src/context_budget.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,14 @@ pub fn apply_context_guard(
145145
let mut compacted = 0;
146146
for loc in &locations {
147147
if loc.char_len > single_max {
148+
// Bounds check: indices may be stale if messages were modified concurrently
149+
if loc.msg_idx >= messages.len() {
150+
continue;
151+
}
148152
if let MessageContent::Blocks(blocks) = &mut messages[loc.msg_idx].content {
153+
if loc.block_idx >= blocks.len() {
154+
continue;
155+
}
149156
if let ContentBlock::ToolResult { content, .. } = &mut blocks[loc.block_idx] {
150157
let old_len = content.len();
151158
*content = truncate_to(content, single_max);
@@ -167,7 +174,13 @@ pub fn apply_context_guard(
167174
if loc.char_len <= compact_target {
168175
continue;
169176
}
177+
if loc.msg_idx >= messages.len() {
178+
continue;
179+
}
170180
if let MessageContent::Blocks(blocks) = &mut messages[loc.msg_idx].content {
181+
if loc.block_idx >= blocks.len() {
182+
continue;
183+
}
171184
if let ContentBlock::ToolResult { content, .. } = &mut blocks[loc.block_idx] {
172185
if content.len() > compact_target {
173186
let old_len = content.len();
@@ -188,11 +201,32 @@ fn truncate_to(content: &str, max_chars: usize) -> String {
188201
if content.len() <= max_chars {
189202
return content.to_string();
190203
}
191-
let keep = max_chars.saturating_sub(80);
204+
let keep = max_chars.saturating_sub(80).min(content.len());
205+
// Ensure keep is a valid char boundary
206+
let keep = if content.is_char_boundary(keep) {
207+
keep
208+
} else {
209+
content[..keep]
210+
.char_indices()
211+
.next_back()
212+
.map(|(i, _)| i)
213+
.unwrap_or(0)
214+
};
215+
let search_start = keep.saturating_sub(100);
216+
// Ensure search_start is a valid char boundary
217+
let search_start = if content.is_char_boundary(search_start) {
218+
search_start
219+
} else {
220+
content[..search_start]
221+
.char_indices()
222+
.next_back()
223+
.map(|(i, _)| i)
224+
.unwrap_or(0)
225+
};
192226
// Try to break at newline
193-
let break_point = content[keep.saturating_sub(100)..keep]
227+
let break_point = content[search_start..keep]
194228
.rfind('\n')
195-
.map(|pos| keep.saturating_sub(100) + pos)
229+
.map(|pos| search_start + pos)
196230
.unwrap_or(keep);
197231
format!(
198232
"{}\n\n[COMPACTED: {} → {} chars by context guard]",

crates/openfang-runtime/src/drivers/anthropic.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -365,10 +365,10 @@ impl LlmDriver for AnthropicDriver {
365365
let mut event_type = String::new();
366366
let mut data = String::new();
367367
for line in event_text.lines() {
368-
if let Some(et) = line.strip_prefix("event: ") {
369-
event_type = et.to_string();
370-
} else if let Some(d) = line.strip_prefix("data: ") {
371-
data = d.to_string();
368+
if let Some(et) = line.strip_prefix("event:") {
369+
event_type = et.trim_start().to_string();
370+
} else if let Some(d) = line.strip_prefix("data:") {
371+
data = d.trim_start().to_string();
372372
}
373373
}
374374

0 commit comments

Comments
 (0)