Skip to content

Commit ceef28b

Browse files
committed
fix(cli): attach system messages to Anthropic request's system field
Previously system messages were silently filtered out. Now they're extracted and passed via the top-level system field per the API spec.
1 parent 0a6a037 commit ceef28b

1 file changed

Lines changed: 47 additions & 3 deletions

File tree

crates/rullm-cli/src/cli_client.rs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,27 @@ use std::pin::Pin;
1515
/// Claude Code identification text for OAuth requests
1616
const CLAUDE_CODE_SPOOF_TEXT: &str = "You are Claude Code, Anthropic's official CLI for Claude.";
1717

18+
/// Extract system messages from conversation, concatenating multiple with double newlines.
19+
/// Returns None if no system messages present.
20+
fn extract_system_content(messages: &[(String, String)]) -> Option<String> {
21+
let system_messages: Vec<&str> = messages
22+
.iter()
23+
.filter_map(|(role, content)| {
24+
if role == "system" {
25+
Some(content.as_str())
26+
} else {
27+
None
28+
}
29+
})
30+
.collect();
31+
32+
if system_messages.is_empty() {
33+
None
34+
} else {
35+
Some(system_messages.join("\n\n"))
36+
}
37+
}
38+
1839
/// Prepend Claude Code system block to an existing system prompt (for OAuth requests)
1940
fn prepend_claude_code_system(existing: Option<SystemContent>) -> SystemContent {
2041
let spoof_block = SystemBlock::text_with_cache(CLAUDE_CODE_SPOOF_TEXT);
@@ -305,12 +326,16 @@ impl CliClient {
305326
config,
306327
is_oauth,
307328
} => {
329+
// Extract system messages first (they go in a top-level field, not in messages)
330+
let user_system = extract_system_content(&messages);
331+
332+
// Filter to only user/assistant messages
308333
let msgs: Vec<AnthropicMessage> = messages
309334
.iter()
310335
.filter_map(|(role, content)| match role.as_str() {
311336
"user" => Some(AnthropicMessage::user(content.as_str())),
312337
"assistant" => Some(AnthropicMessage::assistant(content.as_str())),
313-
_ => None, // Skip system messages for now
338+
_ => None,
314339
})
315340
.collect();
316341

@@ -322,8 +347,27 @@ impl CliClient {
322347
builder = builder.temperature(temp);
323348
}
324349

325-
if *is_oauth {
326-
builder = builder.system_blocks(prepend_claude_code_system(None).into_blocks());
350+
// Attach system content (combining with OAuth prefix if needed)
351+
let system_content = match (user_system, *is_oauth) {
352+
(Some(text), true) => {
353+
// OAuth + user system: prepend Claude Code to user's system
354+
Some(prepend_claude_code_system(Some(SystemContent::Text(
355+
text.into(),
356+
))))
357+
}
358+
(Some(text), false) => {
359+
// No OAuth + user system: just user's system
360+
Some(SystemContent::Text(text.into()))
361+
}
362+
(None, true) => {
363+
// OAuth + no user system: just Claude Code
364+
Some(prepend_claude_code_system(None))
365+
}
366+
(None, false) => None,
367+
};
368+
369+
if let Some(content) = system_content {
370+
builder = builder.system_blocks(content.into_blocks());
327371
}
328372

329373
let request = builder.build();

0 commit comments

Comments
 (0)