Skip to content
Open
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
14 changes: 14 additions & 0 deletions src-tauri/src/core/server/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,20 @@ fn transform_anthropic_to_openai(body: &serde_json::Value) -> Option<serde_json:

let openai_messages = convert_messages(messages, body.get("system"))?;

// Strict chat templates (e.g. AtomicChat/ornith-9b, Qwen3-family GGUFs)
// `raise` "System message must be at the beginning" whenever a request
// carries more than one system message or a non-leading one. Claude Code
// triggers this by combining its system prompt with developer/system items,
// and the failure surfaces during llama.cpp's tool-call parser generation.
// Collapse them into a single leading system message — the same fix already
// applied on the Codex `/responses` path (see responses_shim).
let openai_messages = match openai_messages.as_array() {
Some(arr) => serde_json::Value::Array(super::responses_shim::merge_system_messages(
arr.clone(),
)),
None => openai_messages,
};

let stream = body
.get("stream")
.and_then(|v| v.as_bool())
Expand Down
5 changes: 4 additions & 1 deletion src-tauri/src/core/server/responses_shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ pub fn responses_request_to_chat(body: &Value) -> Value {
/// than one system message or places one after the first turn — which Codex
/// readily does by combining `instructions` with developer/system input items.
/// Order of the remaining (non-system) messages is preserved.
fn merge_system_messages(messages: Vec<Value>) -> Vec<Value> {
///
/// Also reused by the Anthropic `/messages` proxy path (Claude Code), which can
/// likewise emit more than one system message after conversion.
pub(crate) fn merge_system_messages(messages: Vec<Value>) -> Vec<Value> {
let mut system_parts: Vec<String> = Vec::new();
let mut rest: Vec<Value> = Vec::with_capacity(messages.len());

Expand Down