Skip to content
Merged
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
4 changes: 2 additions & 2 deletions src/api/providers/zai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ export class ZAiHandler extends BaseOpenAiCompatibleProvider<string> {

const temperature = this.options.modelTemperature ?? this.defaultTemperature

// Use Z.ai format to preserve reasoning_content and convert post-tool text to system messages
const convertedMessages = convertToZAiFormat(messages, { convertToolResultTextToSystem: true })
// Use Z.ai format to preserve reasoning_content and merge post-tool text into tool messages
const convertedMessages = convertToZAiFormat(messages, { mergeToolResultText: true })

const params: ZAiChatCompletionParams = {
model,
Expand Down
35 changes: 18 additions & 17 deletions src/api/transform/zai-format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ export type ZAiAssistantMessage = AssistantMessage & {
*
* Key differences from standard OpenAI format:
* - Preserves reasoning_content on assistant messages for interleaved thinking
* - Text content after tool_results (like environment_details) is converted to system messages
* instead of user messages, preventing reasoning_content from being dropped
* - Text content after tool_results (like environment_details) is merged into the last tool message
* to avoid creating user messages that would cause reasoning_content to be dropped
*
* @param messages Array of Anthropic messages
* @param options Optional configuration for message conversion
* @param options.convertToolResultTextToSystem If true, convert text content after tool_results
* to system messages instead of user messages.
* This preserves reasoning_content continuity.
* @param options.mergeToolResultText If true, merge text content after tool_results into the last
* tool message instead of creating a separate user message.
* This is critical for Z.ai's interleaved thinking mode.
* @returns Array of OpenAI messages optimized for Z.ai's thinking mode
*/
export function convertToZAiFormat(
messages: AnthropicMessage[],
options?: { convertToolResultTextToSystem?: boolean },
options?: { mergeToolResultText?: boolean },
): Message[] {
const result: Message[] = []

Expand Down Expand Up @@ -96,19 +96,20 @@ export function convertToZAiFormat(

// Handle text/image content after tool results
if (textParts.length > 0 || imageParts.length > 0) {
// For Z.ai interleaved thinking: when convertToolResultTextToSystem is enabled and we have
// tool results followed by text (like environment_details), convert to system message
// instead of user message to avoid dropping reasoning_content.
const shouldConvertToSystem =
options?.convertToolResultTextToSystem && toolResults.length > 0 && imageParts.length === 0
// For Z.ai interleaved thinking: when mergeToolResultText is enabled and we have
// tool results followed by text, merge the text into the last tool message to avoid
// creating a user message that would cause reasoning_content to be dropped.
// This is critical because Z.ai drops all reasoning_content when it sees a user message.
const shouldMergeIntoToolMessage =
Copy link
Contributor

Choose a reason for hiding this comment

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

mergeToolResultText is a behavior change in convertToZAiFormat() but there are no focused unit tests for zai-format.ts like there are for r1-format.ts. Adding a small spec that asserts post-tool text gets merged into the last tool message (and that the default path still creates a user message) would help prevent future regressions.

Fix it with Roo Code or mention @roomote and request a fix.

options?.mergeToolResultText && toolResults.length > 0 && imageParts.length === 0

if (shouldConvertToSystem) {
// Convert text content to system message
const systemMessage: SystemMessage = {
role: "system",
content: textParts.join("\n"),
if (shouldMergeIntoToolMessage) {
// Merge text content into the last tool message
const lastToolMessage = result[result.length - 1] as ToolMessage
if (lastToolMessage?.role === "tool") {
const additionalText = textParts.join("\n")
lastToolMessage.content = `${lastToolMessage.content}\n\n${additionalText}`
}
result.push(systemMessage)
} else {
// Standard behavior: add user message with text/image content
let content: UserMessage["content"]
Expand Down
Loading