Skip to content

fix(config): allow requiresReasoningContentOnAssistantMessages in ModelCompatSchema#89832

Merged
steipete merged 3 commits into
openclaw:mainfrom
KrasimirKralev:fix/89660-compat-reasoning-content-schema
Jun 7, 2026
Merged

fix(config): allow requiresReasoningContentOnAssistantMessages in ModelCompatSchema#89832
steipete merged 3 commits into
openclaw:mainfrom
KrasimirKralev:fix/89660-compat-reasoning-content-schema

Conversation

@KrasimirKralev

@KrasimirKralev KrasimirKralev commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: requiresReasoningContentOnAssistantMessages is consumed at runtime (it gates injecting the DeepSeek reasoning_content replay contract on assistant messages) and is declared on the canonical ModelCompatConfig type, but a custom OpenAI-compatible proxy could not actually use it: (1) the strict Zod ModelCompatSchema rejected the key at startup, and (2) even past validation, getCompat in the active transport ignored the explicit config value for custom providers.
  • Why it matters: without (1), a config replicating native DeepSeek behavior on a custom proxy is rejected with Unrecognized key. Without (2), the key would validate but be silently ignored at runtime for non-auto-detected providers — a startup-success / runtime-failure trap (raised in review).
  • What changed (two coordinated one-liners + tests):
    • src/config/zod-schema.core.ts — add requiresReasoningContentOnAssistantMessages: z.boolean().optional() to ModelCompatSchema, matching the type and the sibling requires* flags.
    • src/agents/openai-transport-stream.tsgetCompat now resolves compat.requiresReasoningContentOnAssistantMessages ?? detected.…, matching openai-completions.ts:1333 and every other field in the same return object (it was the lone field using detected.… alone).
  • What did NOT change (scope boundary): no default behavior change (the flag stays opt-in, auto-detected from URL when unset); no other compat fields; the type already declared the field (packages/llm-core/src/types.ts:383), so no type change was needed; the create-once approval-index write and unrelated transports are untouched.

Linked context

Real behavior proof (required for external PRs)

  • Behavior or issue addressed: the OpenClaw config validator rejected, and the active transport then ignored, a custom-provider config that sets compat.requiresReasoningContentOnAssistantMessages: true.

  • Real environment tested: OpenClaw checked out and pnpm install-ed on Linux, Node v22.20.0. Drove the real production ModelsConfigSchema and the real getCompat resolution used by buildOpenAICompletionsParams.

  • Exact steps or command run after this patch: ran node --import tsx run-validator.mts against the real schema, and exercised the transport getCompat resolution for a custom-proxy model.

  • Evidence after fix: live node console output — the same command was run against the unpatched and patched schema:

    $ node --import tsx run-validator.mts     # BEFORE: schema key absent
    CONFIG REJECTED:
      - providers.my-proxy.models.0.compat: Unrecognized key: "requiresReasoningContentOnAssistantMessages"
    
    $ node --import tsx run-validator.mts     # AFTER: this PR
    CONFIG ACCEPTED by ModelsConfigSchema (gateway would start).
    compat: {"thinkingFormat":"deepseek","requiresReasoningContentOnAssistantMessages":true}
    
  • Observed result after fix: running node against the patched schema prints CONFIG ACCEPTED and the resolved compat carries the flag. For the transport, a custom-proxy model that sets the flag now resolves requiresReasoningContentOnAssistantMessages: true (it resolved false against the pre-fix detected-only code — confirmed by reverting the line), while the same provider without the flag falls back to detection (false).

  • What was not tested: a full openclaw gateway boot against a live DeepSeek proxy (needs real proxy + credentials); the change is config + compat-resolution, both driven directly above.

  • Proof limitations or environment constraints: validator/resolution driven by focused harnesses rather than a daemon boot, to isolate the two code paths the issue and review describe; modules under test are the unmodified production files.

Root cause

  • Root cause: the field was added to the type and runtime compat detection, but never to the strict Zod schema (rejected at startup), and getCompat in openai-transport-stream resolved it from detected only instead of compat.X ?? detected.X like its siblings (ignored at runtime for custom providers).
  • Missing detection / guardrail: the schema file's bidirectional AssertAssignable guards don't catch a missing optional field; and the transport field was the only one not following the compat.X ?? detected.X pattern, so nothing flagged the divergence. The added zod-schema.models.test.ts and openai-transport-stream.test.ts cases now cover both.

@openclaw-barnacle openclaw-barnacle Bot added size: XS triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels Jun 3, 2026
@clawsweeper

clawsweeper Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 4, 2026, 7:39 AM ET / 11:39 UTC.

Summary
The branch adds the optional requiresReasoningContentOnAssistantMessages model-compat key to config validation, honors explicit config in the OpenAI transport compat resolver, and adds focused schema/transport regression tests.

PR surface: Source +3, Tests +60. Total +63 across 4 files.

Reproducibility: yes. Source inspection shows current main's strict schema omits the key and active getCompat ignores the explicit value, and the PR body includes before/after terminal output for the schema plus resolver behavior.

Review metrics: 1 noteworthy metric.

  • Model compat config surface: 1 schema key added, 1 resolver override honored. The PR changes startup validation and provider payload compatibility behavior for configured custom models.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P1] This broadens the accepted model compat config surface; it is opt-in, but custom providers that set the flag will intentionally receive DeepSeek-style assistant reasoning_content replay payloads that green CI cannot prove against every proxy.

Maintainer options:

  1. Accept the opt-in compat surface (recommended)
    Merge once maintainers are comfortable that configured custom providers may explicitly opt into DeepSeek-style assistant reasoning replay payloads.
  2. Require broader proxy proof first
    Ask for a full gateway or live proxy smoke only if maintainers want runtime-provider proof beyond the schema and resolver paths changed here.

Next step before merge

  • Needs maintainer acceptance of the opt-in compatibility surface; no automated repair is identified.

Security
Cleared: The diff changes schema/test/transport resolver logic only and introduces no dependency, secret, CI, package, or code-execution surface.

Review details

Best possible solution:

Land the PR after maintainer acceptance of the opt-in compatibility surface and normal checks, keeping default auto-detection behavior unchanged.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection shows current main's strict schema omits the key and active getCompat ignores the explicit value, and the PR body includes before/after terminal output for the schema plus resolver behavior.

Is this the best way to solve the issue?

Yes. Adding the missing schema key and honoring compat.X ?? detected.X in the active transport is the narrow owner-boundary fix, and it matches the sibling OpenAI completions resolver while preserving defaults.

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against a3af4263532d.

Label changes

Label justifications:

  • P2: This is a normal provider/config compatibility bugfix with limited blast radius and an existing linked user report.
  • merge-risk: 🚨 compatibility: The PR makes a new opt-in config key valid and able to change outgoing assistant replay payloads for custom providers.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes after-fix terminal output from production schema validation and active resolver checks, with before/after behavior described for the changed paths.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal output from production schema validation and active resolver checks, with before/after behavior described for the changed paths.
Evidence reviewed

PR surface:

Source +3, Tests +60. Total +63 across 4 files.

View PR surface stats
Area Files Added Removed Net
Source 2 3 0 +3
Tests 2 60 0 +60
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 4 63 0 +63

What I checked:

Likely related people:

  • Peter Steinberger: git blame on current main points the relevant schema and active transport blocks at f9613ff, the latest visible current-main rewrite/import of both files. (role: recent area contributor; confidence: medium; commits: f9613ff01e2b; files: src/config/zod-schema.core.ts, src/agents/openai-transport-stream.ts)
  • Vincent Koc: Recent release/baseline history touches the compat type, schema, and transport, and the visible transport-seam history added the OpenAI transport module path involved here. (role: adjacent owner; confidence: medium; commits: 2e08f0f4221f, 4798e125f40c; files: packages/llm-core/src/types.ts, src/config/zod-schema.core.ts, src/agents/openai-transport-stream.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 3, 2026
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels Jun 3, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 3, 2026
@KrasimirKralev KrasimirKralev marked this pull request as ready for review June 3, 2026 14:21
KrasimirKralev pushed a commit to KrasimirKralev/openclaw that referenced this pull request Jun 4, 2026
…ges from config

Addresses clawsweeper review on openclaw#89832 (P1 compatibility trap).

getCompat() in openai-transport-stream resolved every compat field as
"compat.X ?? detected.X" EXCEPT requiresReasoningContentOnAssistantMessages,
which used detected.X alone. So a custom OpenAI-compatible proxy (not
auto-detected as DeepSeek/Xiaomi) that set the flag in config would pass
schema validation but be ignored by the active transport — a
startup-success/runtime-failure trap. Now resolves compat.X ?? detected.X,
matching openai-completions.ts:1333 and every sibling field.

Exposes getCompat via the existing test-only testing export and adds a
focused regression: a custom provider with the flag set resolves true
(verified to fail against the pre-fix detected-only code); the same
provider without the flag falls back to detection (false).
@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S and removed size: XS labels Jun 4, 2026
@KrasimirKralev

Copy link
Copy Markdown
Contributor Author

Thanks for the review — good catch on the compatibility trap. Addressed it: getCompat in openai-transport-stream.ts now resolves compat.requiresReasoningContentOnAssistantMessages ?? detected.…, matching openai-completions.ts and every sibling field (it was the only one resolving from detected alone). Added a focused regression in openai-transport-stream.test.ts — a custom provider with the flag set now resolves true (verified to resolve false against the pre-fix code), and falls back to detection when unset. (commit c54c4f3)

@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 4, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 4, 2026
KrasimirKralev and others added 3 commits June 7, 2026 04:45
…elCompatSchema

Closes openclaw#89660.

The field is consumed at runtime (detectCompat/getCompat inject
reasoning_content: "" on assistant messages for DeepSeek) and is
declared on the canonical ModelCompatConfig type
(packages/llm-core/src/types.ts), but was missing from the strict Zod
ModelCompatSchema. A config setting it (to replicate native DeepSeek
behavior on a custom OpenAI-compatible proxy) was rejected at gateway
startup with "Unrecognized key(s) in object".

Add the optional boolean to ModelCompatSchema, matching the existing
type and the other requires* compat flags. The bidirectional
AssertAssignable guard did not catch the gap because a missing
*optional* field does not break type assignability in either
direction.
…ges from config

Addresses clawsweeper review on openclaw#89832 (P1 compatibility trap).

getCompat() in openai-transport-stream resolved every compat field as
"compat.X ?? detected.X" EXCEPT requiresReasoningContentOnAssistantMessages,
which used detected.X alone. So a custom OpenAI-compatible proxy (not
auto-detected as DeepSeek/Xiaomi) that set the flag in config would pass
schema validation but be ignored by the active transport — a
startup-success/runtime-failure trap. Now resolves compat.X ?? detected.X,
matching openai-completions.ts:1333 and every sibling field.

Exposes getCompat via the existing test-only testing export and adds a
focused regression: a custom provider with the flag set resolves true
(verified to fail against the pre-fix detected-only code); the same
provider without the flag falls back to detection (false).
@steipete steipete force-pushed the fix/89660-compat-reasoning-content-schema branch from c54c4f3 to 5bd1474 Compare June 7, 2026 03:50
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 7, 2026
@steipete

steipete commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Land-ready maintainer proof for rebased head 5bd1474d9047619085ef432865dfe4588bfcc931.

What changed:

  • Allowed compat.requiresReasoningContentOnAssistantMessages in the strict model compat schema.
  • Added the same field to ModelCompatConfig, so config/type/runtime stay aligned.
  • Updated the active OpenAI-compatible transport resolver to honor explicit config before auto-detection, matching the sibling completions resolver.
  • Added focused schema and transport regressions for a custom DeepSeek-compatible proxy.

Tested locally before landing:

node scripts/run-vitest.mjs src/config/zod-schema.models.test.ts src/config/config-misc.test.ts src/agents/openai-transport-stream.test.ts src/llm/providers/openai-completions.test.ts src/agents/embedded-agent-runner-extraparams.test.ts
# passed: zod-schema.models 22/22, config tests 89/89, agent/LLM tests 373/373

pnpm tsgo:core
# passed

pnpm exec oxfmt --check src/agents/openai-transport-stream.test.ts src/agents/openai-transport-stream.ts src/config/zod-schema.core.ts src/config/zod-schema.models.test.ts src/config/types.models.ts
# clean

git diff --check origin/main...HEAD
# clean

/Users/steipete/Projects/agent-skills/skills/autoreview/scripts/autoreview --mode branch --base origin/main
# first run found a real missing ModelCompatConfig field; fixed in 5bd1474d904
# rerun clean: no accepted/actionable findings; overall patch is correct (0.86)

Real behavior proof status:

  • I did not run a live custom DeepSeek-compatible proxy because no proxy credential/endpoint is available here.
  • The proof targets the changed product boundary directly: the real config schema accepts the previously rejected compat key, tsgo:core proves the config type includes it, and the active transport resolver now returns the explicit config value for a custom provider while preserving fallback detection when unset.

CI note:

  • Fresh CI is queued for the rebased head. I am landing with maintainer bypass on focused local proof plus clean autoreview; this PR’s prior ClawSweeper review had proof marked sufficient and no automated repair remaining after the type fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: requiresReasoningContentOnAssistantMessages missing from ModelCompatSchema — can't replicate native DeepSeek behavior on custom providers

2 participants