Skip to content

[Bug]: ZAI GLM-5 reasoning models return empty responses — reasoning_content routed to thinking instead of text (regression from 2026.4.24+) #76048

Description

@Timluogit

Summary

ZAI GLM-5 reasoning models (glm-5-turbo, glm-5.1) return empty/incoherent responses in OpenClaw 2026.4.29. The API returns all actual text in reasoning_content field with content="", but the stream processing pipeline routes reasoning_content to appendThinkingDelta() (hidden) instead of appendTextDelta() (visible text).

Environment

  • OpenClaw: 2026.4.29 (latest as of 2026-05-02)
  • Primary model: zai/glm-5-turbo
  • Channel: Feishu (likely affects all channels)
  • ZAI baseUrl: https://open.bigmodel.cn/api/coding/paas/v4

Problem to solve

ZAI API response format

GLM-5 reasoning models return responses like:

{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "",
      "reasoning_content": "This is the actual user-facing text the model generated"
    }
  }],
  "usage": { "completion_tokens": 200, "reasoning_tokens": 198 }
}

All meaningful text is in reasoning_content, while content is empty.

OpenClaw stream processing behavior

In openai-transport-stream-aPa0aR5w.js, the stream pipeline:

  1. processOpenAICompletionsStream()getCompletionsReasoningDeltas() → captures reasoning_content from delta chunks
  2. getCompletionsReasoningDeltas() tags these as kind: "thinking"
  3. The routing at ~line 1845 sends them to appendThinkingDelta() (hidden from user)
  4. Since content="", no text reaches appendTextDelta() (visible to user)
  5. Result: empty response sent to user

The dead compat flag

provider-model-compat-*.js correctly sets thinkingFormat: "zai" for ZAI provider. The getCompat() function merges this into the compat object. However, the stream processing code never checks compat.thinkingFormat when deciding whether to route reasoning_content as text vs thinking.

The requiresThinkingAsText compat flag also exists in the Zod schema and model config normalization, but is similarly never consumed by any stream processing code path.

ZAI plugin behavior

The ZAI plugin's resolveThinkingProfile() returns defaultLevel: "off", which sends payload.thinking = { type: "disabled" } to the API. However, GLM-5 reasoning models still produce reasoning_content regardless of this setting.

Regression

This worked correctly before v2026.4.24. The stream processing pipeline was restructured around that version to support thinking/reasoning across providers, and ZAI's reasoning_content handling was broken as a result.

Related issues

Proposed fix

The thinkingFormat compat flag already exists and is correctly set to "zai" for ZAI provider. The fix should consume this flag in the stream processing routing decision:

// In processOpenAICompletionsStream(), ~line 1845:
// Before (broken for ZAI):
if (reasoningDelta.kind === "text") appendTextDelta(reasoningDelta.text);
else appendThinkingDelta(reasoningDelta);

// After (respects thinkingFormat compat):
if (reasoningDelta.kind === "text" || compat.thinkingFormat === "zai") appendTextDelta(reasoningDelta.text);
else appendThinkingDelta(reasoningDelta);

This is consistent with the direction proposed in #75105 — using compat flags to control per-provider reasoning behavior rather than hardcoding to specific provider/model names.

Current workaround

Patching openai-transport-stream-aPa0aR5w.js with the above change (in both npm and plugin-runtime-deps paths). Patch is overwritten on npm update openclaw.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions