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:
processOpenAICompletionsStream() → getCompletionsReasoningDeltas() → captures reasoning_content from delta chunks
getCompletionsReasoningDeltas() tags these as kind: "thinking"
- The routing at ~line 1845 sends them to
appendThinkingDelta() (hidden from user)
- Since
content="", no text reaches appendTextDelta() (visible to user)
- 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.
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 inreasoning_contentfield withcontent="", but the stream processing pipeline routesreasoning_contenttoappendThinkingDelta()(hidden) instead ofappendTextDelta()(visible text).Environment
zai/glm-5-turbohttps://open.bigmodel.cn/api/coding/paas/v4Problem to solve
ZAI API response format
GLM-5 reasoning models return responses like:
All meaningful text is in
reasoning_content, whilecontentis empty.OpenClaw stream processing behavior
In
openai-transport-stream-aPa0aR5w.js, the stream pipeline:processOpenAICompletionsStream()→getCompletionsReasoningDeltas()→ capturesreasoning_contentfrom delta chunksgetCompletionsReasoningDeltas()tags these askind: "thinking"appendThinkingDelta()(hidden from user)content="", no text reachesappendTextDelta()(visible to user)The dead compat flag
provider-model-compat-*.jscorrectly setsthinkingFormat: "zai"for ZAI provider. ThegetCompat()function merges this into the compat object. However, the stream processing code never checkscompat.thinkingFormatwhen deciding whether to routereasoning_contentas text vs thinking.The
requiresThinkingAsTextcompat 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()returnsdefaultLevel: "off", which sendspayload.thinking = { type: "disabled" }to the API. However, GLM-5 reasoning models still producereasoning_contentregardless 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_contenthandling was broken as a result.Related issues
reasoning_contentfix #75105 — Feature request for per-modelcompat.reasoningFormatsetting (same architectural gap)reasoning_contentwrapper only applies toprovider=deepseekextra_bodythinking field collision affecting all providersProposed fix
The
thinkingFormatcompat flag already exists and is correctly set to"zai"for ZAI provider. The fix should consume this flag in the stream processing routing decision: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.jswith the above change (in both npm and plugin-runtime-deps paths). Patch is overwritten onnpm update openclaw.