openclaw - ✅(Solved) Fix [Bug]: openai-completions custom API (school CSU) returns JSON-wrapped response instead of plain text [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#43657Fetched 2026-04-08 00:16:15
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Participants
Timeline (top)
labeled ×2cross-referenced ×1

When using a custom openai-completions provider, the agent response is returned as a nested JSON-serialized array instead of plain text.

Root Cause

When using a custom openai-completions provider, the agent response is returned as a nested JSON-serialized array instead of plain text.

Fix Action

Fix / Workaround

The models.json file is absent from ~/.openclaw/agents/main/agent/, which may indicate the agent was never properly initialized with the custom provider config. Possibly related to: #34134 (openai-completions switching invalid). No workaround currently found. Downgrading to an earlier version is being considered.

PR fix notes

PR #43672: Fix JSON-wrapped assistant text for openai-completions custom APIs

Description (problem / solution / changelog)

Summary

  • Problem: Some custom openai-completions backends return assistant text as serialized chat-content blocks (for example [{"type":"text","text":"..."}] or JSON5/python-style variants), which was surfaced verbatim to end users.
  • Why it matters: Web/chat users receive nested JSON-wrapped payloads instead of readable plain text replies.
  • What changed: Added a strict unwrapping step in extractAssistantText to parse serialized text-block arrays (including nested wraps) and extract the plain text before existing sanitization.
  • What did NOT change (scope boundary): No model routing/provider selection changes and no transport/protocol changes.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #43657
  • Related #34134

User-visible / Behavior Changes

  • Assistant replies that arrive as serialized text-block arrays are now unwrapped and shown as plain text.

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS (dev)
  • Runtime/container: Node 22 + Vitest
  • Model/provider: OpenAI-compatible path (openai-completions)
  • Integration/channel (if any): Embedded runner assistant text extraction
  • Relevant config (redacted): N/A (unit-level coverage)

Steps

  1. Construct assistant message text that matches the nested serialized payload shape reported in #43657.
  2. Call extractAssistantText.
  3. Assert returned text is fully unwrapped plain text.

Expected

  • Plain text output (no JSON/list wrapper leakage).

Actual

  • Regression test now passes and returns plain text.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: Nested serialized text blocks with JSON5/python-style quoting are unwrapped to plain text via extractAssistantText.
  • Edge cases checked: Non-serialized normal assistant text continues through existing sanitization path; unwrapping is bounded and requires typed-block array structure.
  • What you did not verify: End-to-end Docker/webchat reproduction against the reporter's custom provider.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Revert this PR commit.
  • Files/config to restore: src/agents/pi-embedded-utils.ts
  • Known bad symptoms reviewers should watch for: Assistant messages that are intentionally literal serialized block arrays could be unwrapped unexpectedly.

Risks and Mitigations

  • Risk: Over-eager unwrapping could alter literal text that looks like a chat-content array.
    • Mitigation: Unwrapping only applies when parsing succeeds and value is a typed block array with at least one non-empty text block; loop is bounded.

Validation Commands Run

  • pnpm test src/agents/pi-embedded-utils.test.ts
    • Result: pass (1 file, 34 tests)

Changed files

  • src/agents/pi-embedded-utils.test.ts (modified, +68/-0)
  • src/agents/pi-embedded-utils.ts (modified, +59/-1)

Code Example

{
  "models": {
    "providers": {
      "custom-school": {
        "baseUrl": "<REDACTED_CUSTOM_API_BASE_URL>",
        "apiKey": "${CUSTOM_API_KEY}",
        "api": "openai-completions",
        "models": [
          {
            "id": "deepseek-v3",
            "name": "DeepSeek V3",
            "reasoning": false,
            "input": ["text"],
            "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
            "contextWindow": 65536,
            "maxTokens": 8192
          }
        ]
      }
    }
  }
}

---

[{'type': 'text', 'text': '[{\'type\': \'text\', \'text\': \'当前时间是:\n- **UTC 时间**:2026年3月12日 03:06 AM\n- **北京时间**:2026年3月12日 11:06 AM(UTC+8)\'}]'}]

---

# curl verification (API works correctly, URL redacted):
$ curl -X POST <REDACTED_CUSTOM_API_BASE_URL>/chat/completions \
  -H "Authorization: Bearer sk-****" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v3",
    "messages": [{"role": "user", "content": "你好"}],
    "stream": false
  }'

# Response (valid, plain text in content field):
{
  "id": "3f7c4bb1...",
  "object": "chat.completion",
  "model": "deepseek-v3",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "你好!很高兴见到你!有什么可以帮你的吗?😊"
    },
    "finish_reason": "stop"
  }]
}

# Agent directory listing (models.json missing):
$ ls -al ~/.openclaw/agents/main/agent/
total 8
drwxr-xr-x 2 user Administrators 4096 Mar 11 21:08 .
drwxr-xr-x 4 user Administrators 4096 Mar 11 21:08 ..
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Summary

When using a custom openai-completions provider, the agent response is returned as a nested JSON-serialized array instead of plain text.

Steps to reproduce

  1. Configure a custom openai-completions provider in ~/.openclaw/openclaw.json:
{
  "models": {
    "providers": {
      "custom-school": {
        "baseUrl": "<REDACTED_CUSTOM_API_BASE_URL>",
        "apiKey": "${CUSTOM_API_KEY}",
        "api": "openai-completions",
        "models": [
          {
            "id": "deepseek-v3",
            "name": "DeepSeek V3",
            "reasoning": false,
            "input": ["text"],
            "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
            "contextWindow": 65536,
            "maxTokens": 8192
          }
        ]
      }
    }
  }
}
  1. Start OpenClaw via Docker (ghcr.io/openclaw/openclaw:v2026.3.7).
  2. Send any user message via webchat (e.g. "What time is it?").
  3. Observe the agent response.

Expected behavior

The agent replies with plain text, e.g.: "The current time is 11:06 AM (UTC+8)."

Actual behavior

The agent response is a nested JSON-serialized array instead of plain text:

[{'type': 'text', 'text': '[{\'type\': \'text\', \'text\': \'当前时间是:\n- **UTC 时间**:2026年3月12日 03:06 AM\n- **北京时间**:2026年3月12日 11:06 AM(UTC+8)\'}]'}]

The outer layer is a Python-style list-of-dict structure, and the inner text field contains another JSON-escaped string. This is never rendered as plain text to the end user.

OpenClaw version

v2026.3.8

Operating system

NAS (fnos), Docker container

Install method

Docker (ghcr.io/openclaw/openclaw:latest)

Model

deepseek-v3

Provider / routing chain

openclaw -> custom openai-completions provider (university self-hosted API, redacted) -> deepseek-v3

Config file / key location

~/.openclaw/openclaw.json ; models.providers.custom-school.baseUrl (redacted) ; ~/.openclaw/agents/main/agent/models.json (file does not exist — directory is empty)

Additional provider/model setup details

  • Provider type: openai-completions (custom, university-hosted, OpenAI-compatible)
  • The API endpoint was verified to work correctly via curl with stream: false, returning a valid standard OpenAI-format JSON response with choices[0].message.content as plain text.
  • The ~/.openclaw/agents/main/agent/ directory contains only . and ..models.json is absent, which may be related.
  • Possibly related to issue #34134.

Logs, screenshots, and evidence

# curl verification (API works correctly, URL redacted):
$ curl -X POST <REDACTED_CUSTOM_API_BASE_URL>/chat/completions \
  -H "Authorization: Bearer sk-****" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v3",
    "messages": [{"role": "user", "content": "你好"}],
    "stream": false
  }'

# Response (valid, plain text in content field):
{
  "id": "3f7c4bb1...",
  "object": "chat.completion",
  "model": "deepseek-v3",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "你好!很高兴见到你!有什么可以帮你的吗?😊"
    },
    "finish_reason": "stop"
  }]
}

# Agent directory listing (models.json missing):
$ ls -al ~/.openclaw/agents/main/agent/
total 8
drwxr-xr-x 2 user Administrators 4096 Mar 11 21:08 .
drwxr-xr-x 4 user Administrators 4096 Mar 11 21:08 ..

Impact and severity

Affected: users of OpenClaw with a custom openai-completions provider. Severity: High — the agent is completely unusable; all responses are garbled JSON instead of readable text. Frequency: 100% reproducible. Consequence: Agent cannot produce any usable reply when using a custom openai-completions API.

Additional information

The models.json file is absent from ~/.openclaw/agents/main/agent/, which may indicate the agent was never properly initialized with the custom provider config. Possibly related to: #34134 (openai-completions switching invalid). No workaround currently found. Downgrading to an earlier version is being considered.

extent analysis

Fix Plan

To resolve the issue of the agent response being returned as a nested JSON-serialized array instead of plain text, follow these steps:

  1. Verify the API Response: Ensure that the custom OpenAI-completions provider API returns a valid JSON response with the choices[0].message.content field containing plain text.
  2. Check the models.json File: Confirm that the models.json file is present in the ~/.openclaw/agents/main/agent/ directory. If it's missing, create it with the necessary configuration.
  3. Update the openclaw.json Configuration: Modify the openclaw.json file to include the correct configuration for the custom OpenAI-completions provider.

Example models.json file:

{
  "models": [
    {
      "id": "deepseek-v3",
      "name": "DeepSeek V3",
      "reasoning": false,
      "input": ["text"],
      "cost": {
        "input": 0,
        "output": 0,
        "cacheRead": 0,
        "cacheWrite": 0
      },
      "contextWindow": 65536,
      "maxTokens": 8192
    }
  ]
}

Example openclaw.json configuration:

{
  "models": {
    "providers": {
      "custom-school": {
        "baseUrl": "<REDACTED_CUSTOM_API_BASE_URL>",
        "apiKey": "${CUSTOM_API_KEY}",
        "api": "openai-completions",
        "models": [
          {
            "id": "deepseek-v3",
            "name": "DeepSeek V3",
            "reasoning": false,
            "input": ["text"],
            "cost": {
              "input": 0,
              "output": 0,
              "cacheRead": 0,
              "cacheWrite": 0
            },
            "contextWindow": 65536,
            "maxTokens": 8192
          }
        ]
      }
    }
  }
}

Verification

To verify that the fix worked:

  1. Restart the OpenClaw service.
  2. Send a user message via webchat.
  3. Check the agent response to ensure it's in plain text format.

Extra Tips

  • Ensure that the custom OpenAI-completions provider API is correctly configured and returns valid JSON responses.
  • Verify that the models.json file is present and correctly configured in the ~/.openclaw/agents/main/agent/ directory.
  • If issues persist, consider downgrading to an earlier version of OpenClaw or seeking further assistance.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

The agent replies with plain text, e.g.: "The current time is 11:06 AM (UTC+8)."

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Bug]: openai-completions custom API (school CSU) returns JSON-wrapped response instead of plain text [1 pull requests, 1 participants]