Skip to content

fix(providers): OpenRouter reasoning-model content:null fallback - #1155

Closed
dizhaky wants to merge 1 commit into
rohitg00:mainfrom
dizhaky:dan/openrouter-reasoning-fallback
Closed

fix(providers): OpenRouter reasoning-model content:null fallback#1155
dizhaky wants to merge 1 commit into
rohitg00:mainfrom
dizhaky:dan/openrouter-reasoning-fallback

Conversation

@dizhaky

@dizhaky dizhaky commented Aug 5, 2026

Copy link
Copy Markdown

Problem

OpenRouterProvider.call() reads only message.content:

const content = choices?.[0]?.message?.content;
if (!content) {
  throw new Error(`${this.name} returned unexpected response: ...`);
}
return content;

Reasoning models on OpenRouter (DeepSeek V4/R1, etc.) return the final answer in message.content, but when reasoning exhausts the token budget (finish_reason: "length"), content is null and only message.reasoning / message.reasoning_details are populated. This makes the provider throw "returned unexpected response", surfacing as empty_provider_response from mem::summarize chunk calls. The chunk+skip+reduce layer absorbs the failures (sessions still produce valid summaries), but it wastes a retry per failing chunk — observed ~227 chunk-level mem::summarize failures over ~26h on one deployment.

Fix

Fall back to reasoning_details[].text ?? message.reasoning when content is null, so the chunk gets degraded-but-usable output instead of a hard throw:

const msg = choices?.[0]?.message;
let content = msg?.content;
if (!content) {
  const details = msg?.reasoning_details;
  content = (Array.isArray(details) ? details.find((d) => d.text)?.text : undefined)
    ?? msg?.reasoning ?? undefined;
}
if (!content) { throw new Error(`${this.name} returned unexpected response: ...`); }
return content;

The reasoning text is truncated in the length case, but the chunk+reduce layer tolerates partial output better than a hard throw; when the model finishes (finish_reason: "stop") content is populated and the fallback is not used.

Verification

Forced the content:null case against the live OpenRouter API (deepseek-v4-pro, max_tokens=20finish_reason: "length", content: null, reasoning_details populated). The patched extraction returns the reasoning text instead of throwing. Deployed in a coolify Docker image on mfc1; container rebuilt + healthy, code_memory_consolidate succeeds.

Deploy-time dist patch (included)

The coolify Dockerfile installs the pre-built npm package (@agentmemory/agentmemory@<ver>) rather than building from source, so this PR also adds deploy/coolify/openrouter-reasoning-patch.mjs + a Dockerfile RUN that applies the same fix to the bundled dist/*.mjs at image-build time. This lets existing coolify deploys get the fix before a new npm release ships the compiled source change. The source fix is the real fix; the dist patch is a stopgap for pre-built-package deploys.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of OpenRouter reasoning-model responses when standard message content is unavailable.
    • Responses can now use available reasoning text as a fallback, preventing valid answers from being incorrectly treated as empty or invalid.
    • Added deployment-time compatibility support for packaged builds.

OpenRouterProvider.call() read only message.content. Reasoning models on
OpenRouter (DeepSeek V4/R1, etc.) return content:null with the answer in
message.reasoning / message.reasoning_details when finish_reason:"length"
(reasoning exhausts the token budget before producing final content). This
caused mem::summarize chunk calls to hard-fail with empty_provider_response
(absorbed by the chunk+skip+reduce layer, but wasteful ~227 retries).

Fall back to reasoning_details[].text ?? message.reasoning when content is
null so the chunk gets degraded-but-usable output instead of a hard throw.
The reasoning text is truncated in the length case, but the chunk+reduce
layer tolerates partial output better than a throw; when the model finishes
(finish_reason:"stop") content is populated and the fallback is not used.

Also adds a deploy-time dist patch (deploy/coolify/openrouter-reasoning-patch.mjs
+ Dockerfile RUN) for the coolify Docker image, which installs the pre-built
npm package rather than building from source — so the fix reaches running
containers before a new npm release ships the compiled source fix.

Verified live on mfc1: forced content:null response (max_tokens=20 ->
finish_reason:"length") -> patched extraction returns reasoning (89 chars)
instead of throwing. Container rebuilt + healthy.

DAN-2499

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

@dizhaky is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The OpenRouter provider now falls back to reasoning fields when response content is absent. The Coolify Docker build patches compiled AgentMemory files with the same behavior.

Changes

OpenRouter reasoning fallback

Layer / File(s) Summary
Provider response parsing
src/providers/openrouter.ts
The provider accepts nullable content and optional reasoning fields. It uses content first, then reasoning_details[].text or reasoning.
Coolify distribution patch
deploy/coolify/openrouter-reasoning-patch.mjs, deploy/coolify/Dockerfile
The build patches matching distribution files, reports replacement results, fails when no target is found, and removes the temporary script.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: rohitg00

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a fallback for OpenRouter reasoning-model responses with null content.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/providers/openrouter.ts (1)

72-78: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove implementation comments from the TypeScript provider.

Lines 72-78 explain the code behavior. Remove them and keep the fallback control flow self-explanatory.

As per coding guidelines, “Do not add comments explaining what code does; use clear naming instead.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/providers/openrouter.ts` around lines 72 - 78, Remove the explanatory
comment above the fallback logic in the OpenRouter provider, leaving the
existing control flow unchanged and relying on its naming and structure to
convey the behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/providers/openrouter.ts`:
- Around line 72-78: Remove the explanatory comment above the fallback logic in
the OpenRouter provider, leaving the existing control flow unchanged and relying
on its naming and structure to convey the behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c716131-a7fe-4903-9003-60d9b85e7b3c

📥 Commits

Reviewing files that changed from the base of the PR and between d60652a and ce22268.

📒 Files selected for processing (3)
  • deploy/coolify/Dockerfile
  • deploy/coolify/openrouter-reasoning-patch.mjs
  • src/providers/openrouter.ts

@dizhaky dizhaky closed this Aug 6, 2026
@dizhaky
dizhaky deleted the dan/openrouter-reasoning-fallback branch August 6, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant