Skip to content
Closed
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
3 changes: 3 additions & 0 deletions src/llm/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1353,6 +1353,9 @@ fn convert_messages_to_openai(messages: &OneOrMany<Message>) -> Vec<serde_json::
}
if !tool_calls.is_empty() {
msg["tool_calls"] = serde_json::json!(tool_calls);
// Moonshot AI requires reasoning_content field when thinking is enabled
// and tool_calls are present. Add empty string to satisfy API requirement.
msg["reasoning_content"] = serde_json::json!("");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any concern about sending reasoning_content to non-Kimi endpoints? Some OpenAI-compatible providers can be strict about unknown fields. Since stream_openai() already computes is_kimi_endpoint, it might be worth gating this (or threading a flag into convert_messages_to_openai) so we only add it for Moonshot/Kimi.

Comment on lines 1354 to +1358

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Show every call site that uses the shared OpenAI serializer.
rg -n -C2 'convert_messages_to_openai\(&request\.chat_history\)' src/llm/model.rs

Repository: spacedriveapp/spacebot

Length of output: 562


🏁 Script executed:

#!/bin/bash
# Identify which functions contain each call site
echo "=== Context around line 607 ==="
sed -n '580,620p' src/llm/model.rs | head -50

echo -e "\n=== Context around line 859 ==="
sed -n '830,870p' src/llm/model.rs | head -50

echo -e "\n=== Context around line 951 ==="
sed -n '920,970p' src/llm/model.rs | head -50

Repository: spacedriveapp/spacebot

Length of output: 5019


🏁 Script executed:

#!/bin/bash
# Show the modified lines and surrounding context
echo "=== Modified section lines 1350-1365 ==="
sed -n '1350,1365p' src/llm/model.rs | cat -n

echo -e "\n=== Function signature and earlier context ==="
sed -n '1300,1320p' src/llm/model.rs | cat -n

Repository: spacedriveapp/spacebot

Length of output: 1984


🏁 Script executed:

#!/bin/bash
# Get the function signature and beginning to understand parameter availability
echo "=== Function signature ==="
sed -n '1200,1230p' src/llm/model.rs | cat -n

echo -e "\n=== Check if this function receives any provider context ==="
rg -n "fn convert_messages_to_openai" src/llm/model.rs -A 5

Repository: spacedriveapp/spacebot

Length of output: 1893


🏁 Script executed:

#!/bin/bash
# Check if there's a separate Moonshot-specific serializer or if this is the only path
echo "=== Search for Moonshot-specific message conversion ==="
rg -n "moonshot\|kimi" src/llm/model.rs -i

echo -e "\n=== Check what providers use stream_openai ==="
rg -n "fn stream_openai\|Provider::" src/llm/model.rs | head -30

echo -e "\n=== Look for provider enum definition to understand available types ==="
rg -n "enum Provider\|pub enum ApiType" src/llm/model.rs -A 20 | head -50

Repository: spacedriveapp/spacebot

Length of output: 242


🏁 Script executed:

#!/bin/bash
# Let's look at the struct/impl context around stream_openai
echo "=== Find the impl struct for stream_openai ==="
sed -n '550,610p' src/llm/model.rs

echo -e "\n=== Check what Provider types exist ==="
rg -n "Provider::" src/llm/model.rs | head -20

Repository: spacedriveapp/spacebot

Length of output: 2351


🏁 Script executed:

#!/bin/bash
# Look at the struct definition that implements stream_openai
echo "=== Search for struct containing stream_openai impl ==="
rg -n "impl.*{" src/llm/model.rs | grep -A 5 -B 5 stream_openai

echo -e "\n=== Look for the self.provider type ==="
sed -n '1,100p' src/llm/model.rs | grep -E "struct|enum|Provider|pub"

echo -e "\n=== Check imports to find Provider definition ==="
head -50 src/llm/model.rs

Repository: spacedriveapp/spacebot

Length of output: 2641


🏁 Script executed:

#!/bin/bash
# Check how stream_openai is called and what providers exist
echo "=== Look for where stream_openai is invoked ==="
rg -n "self\.stream_openai\|stream_openai(" src/llm/model.rs | head -20

echo -e "\n=== Check if provider field is used to gate behavior ==="
sed -n '580,700p' src/llm/model.rs | grep -E "provider|Provider" -i

echo -e "\n=== Check the whole convert_messages_to_openai function ==="
sed -n '1272,1365p' src/llm/model.rs

Repository: spacedriveapp/spacebot

Length of output: 5339


🏁 Script executed:

#!/bin/bash
# Check if there are any tests for this serialization, especially for tool_calls
echo "=== Look for tests of convert_messages_to_openai ==="
rg -n "convert_messages_to_openai\|#\[test\]|#\[tokio::test\]" src/llm/model.rs | tail -50

echo -e "\n=== Check if reasoning_content appears anywhere else ==="
rg -n "reasoning_content" src/llm/

echo -e "\n=== Check what the different call sites are used for ==="
grep -n "fn call_openai\|fn stream_openai_compatible\|fn stream_openai_compatible_with_optional_auth" src/llm/model.rs -A 3

Repository: spacedriveapp/spacebot

Length of output: 2178


🏁 Script executed:

#!/bin/bash
# Check if provider information is available at the convert_messages_to_openai call sites
echo "=== Check if provider info is passed to convert_messages_to_openai ==="
sed -n '600,615p' src/llm/model.rs

echo -e "\n=== Check stream_openai_compatible signature and what info it has ==="
sed -n '825,875p' src/llm/model.rs | head -60

echo -e "\n=== Check if there's any Moonshot/Kimi-specific configuration ==="
rg -n "moonshot\|kimi" src/config/ -i

Repository: spacedriveapp/spacebot

Length of output: 2836


Gate this Moonshot-specific field to the appropriate call site, or verify all OpenAI-compatible backends accept it.

convert_messages_to_openai() receives only message data—no provider context—so the reasoning_content: "" field is now sent unconditionally to every assistant message with tool_calls across all three call paths: stream_openai(), stream_openai_compatible(), and stream_openai_compatible_with_optional_auth(). This affects OpenAI, Gemini-compatible, and other optional-auth backends, not just Moonshot. Either pass provider information to the serializer and gate the field by provider type, or explicitly verify that OpenAI and other compatible backends tolerate the extra key before sending it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/llm/model.rs` around lines 1354 - 1358, convert_messages_to_openai() is
unconditionally injecting "reasoning_content": "" when tool_calls exist, which
sends a Moonshot-specific field to all backends; change the serializer to accept
provider/backend context (e.g., add a provider parameter or Backend enum) and
only insert reasoning_content when the provider indicates Moonshot (or a backend
that requires it). Update call sites that invoke convert_messages_to_openai() —
stream_openai(), stream_openai_compatible(), and
stream_openai_compatible_with_optional_auth() — to pass the provider/backend
info, and gate the msg["reasoning_content"] assignment behind a check like if
provider == Provider::Moonshot (or provider.requires_reasoning_content()) so
OpenAI/Gemini-compatible backends do not receive the extra key.

}
result.push(msg);
}
Expand Down