Skip to content

fix(claude): preserve tool_result adjacency in native and CC-compatible paths - #1555

Merged
diegosouzapw merged 4 commits into
diegosouzapw:release/v3.7.0from
congvc-dev:fix/claude-tool-result-adjacency
Apr 24, 2026
Merged

diegosouzapw merged 4 commits into
diegosouzapw:release/v3.7.0from
congvc-dev:fix/claude-tool-result-adjacency

Conversation

@congvc-dev

@congvc-dev congvc-dev commented Apr 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • preserve structured tool_result blocks for native Claude passthrough requests
  • keep Claude Code-compatible message normalization from collapsing tool_use / tool_result adjacency
  • promote system / developer source messages into top-level Claude system blocks without corrupting tool history
  • add regression coverage for both native Claude passthrough and Claude Code-compatible request building

Problem

Claude-native tool use is strict: when an assistant emits tool_use, the immediately following user turn must contain the matching tool_result blocks.

OmniRoute was mutating this history in two places:

  • native Claude passthrough normalized tool_result blocks into plain text ([Tool Result: ...])
  • Claude Code-compatible request building could merge or trim turns too aggressively around tool-use boundaries

That caused Anthropic 400s like:

  • tool_use ids were found without tool_result blocks immediately after

Scope

This fix is not specific to claude-opus-4-7.

It applies to the native Claude provider path and the Claude Code-compatible request builder, so it affects Claude-family models that travel through those paths, including Sonnet/Opus/Haiku variants on Anthropic-native routing.

The live reproduction I used during diagnosis happened on cc/claude-opus-4-7, but the code changes themselves are provider/path scoped rather than model-id scoped.

Fix

  • add a narrow passthrough-only option in normalizeClaudeUpstreamMessages() to preserve structured tool_result blocks for native Claude requests
  • keep existing text-normalization behavior for translated OpenAI->Claude paths
  • make Claude Code-compatible message merging aware of tool_use / tool_result boundaries
  • preserve trailing assistant tool_use turns that are still awaiting the next user tool_result
  • extract source system / developer messages cleanly into Claude top-level system

Validation

  • node --import tsx/esm --test tests/unit/claude-code-compatible-request.test.ts
  • regression coverage in:
    • tests/unit/claude-code-compatible-request.test.ts
    • tests/unit/chatcore-sanitization.test.ts
  • live local replay against /v1/messages with native Claude routing and assistant tool_use + next-user tool_result returned 200 after the patch

Note

The live reproduction used cc/claude-opus-4-7 because that was the failing route available during debugging. That should be read as validated example coverage, not as a model-specific fix.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces account-level concurrency limiting via a semaphore system, integrated into both streaming and non-streaming request paths in chatCore.ts. It also enhances Claude-specific message processing by adding content stripping for incompatible types, preserving tool_result blocks during passthrough, and improving the extraction of system messages from OpenAI-style payloads. Additionally, the changes include improved tracking of pending requests, audit logging for provider warnings, and synchronization of Claude extra-usage states. Feedback was provided regarding the robustness of the semaphore release mechanism in the streaming path, specifically to ensure resources are freed during stream cancellations or when handling empty bodies.

Comment on lines +2006 to +2025
if (stream) {
const originalBody = res.response.body;
const wrappedBody = originalBody
? originalBody.pipeThrough(
new TransformStream({
flush: () => {
acquireAccountSemaphoreRelease();
},
})
)
: null;
return {
...res,
response: new Response(wrappedBody, {
status: res.response.status,
statusText: res.response.statusText,
headers: res.response.headers,
}),
};
}

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.

high

Implement a robust release mechanism for the streaming path that handles empty bodies and stream cancellation (client disconnects).

References
  1. Resources like semaphores or locks must be released in all terminal states, including success, error, and cancellation.

Register Nous Research as an OpenAI-compatible gateway with remote
model discovery and validation against chat completions.

Add Petals provider metadata, default config, validation, and a
specialized executor that maps OpenAI-style requests to the public
generate endpoint. Also allow optional API keys and configurable base
URLs for Petals in the dashboard and provider schemas.

Expand provider model and catalog tests to cover both integrations.
Await runtime request queue updates so limiter settings and auto-enabled
API key protections are recomputed when resilience settings change.

Preserve cancelled batch state for in-flight work by marking input files
processed without generating output artifacts, and replace cached synced
models with an empty set when remote discovery returns no models so the
providers route falls back to the local catalog instead of stale cache.
@diegosouzapw
diegosouzapw changed the base branch from main to release/v3.7.0 April 24, 2026 12:15
@diegosouzapw
diegosouzapw merged commit 15b4a54 into diegosouzapw:release/v3.7.0 Apr 24, 2026
1 check passed
@diegosouzapw

Copy link
Copy Markdown
Owner

Great work! 🚀 I've merged this into release/v3.7.0. The adjacency fix will definitely improve the stability of our Claude Code integration. Thank you for the contribution!

@congvc-dev
congvc-dev deleted the fix/claude-tool-result-adjacency branch April 24, 2026 15:09
@diegosouzapw diegosouzapw mentioned this pull request Apr 25, 2026
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
* fix(claude): preserve tool_result adjacency in native and CC-compatible paths

* feat(providers): add Petals and Nous Research provider support

Register Nous Research as an OpenAI-compatible gateway with remote
model discovery and validation against chat completions.

Add Petals provider metadata, default config, validation, and a
specialized executor that maps OpenAI-style requests to the public
generate endpoint. Also allow optional API keys and configurable base
URLs for Petals in the dashboard and provider schemas.

Expand provider model and catalog tests to cover both integrations.

* fix(resilience): sync queue updates and clear stale discovery caches

Await runtime request queue updates so limiter settings and auto-enabled
API key protections are recomputed when resilience settings change.

Preserve cancelled batch state for in-flight work by marking input files
processed without generating output artifacts, and replace cached synced
models with an empty set when remote discovery returns no models so the
providers route falls back to the local catalog instead of stale cache.

---------

Co-authored-by: congvc <congvc-dev@gmail.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
* fix(claude): preserve tool_result adjacency in native and CC-compatible paths

* feat(providers): add Petals and Nous Research provider support

Register Nous Research as an OpenAI-compatible gateway with remote
model discovery and validation against chat completions.

Add Petals provider metadata, default config, validation, and a
specialized executor that maps OpenAI-style requests to the public
generate endpoint. Also allow optional API keys and configurable base
URLs for Petals in the dashboard and provider schemas.

Expand provider model and catalog tests to cover both integrations.

* fix(resilience): sync queue updates and clear stale discovery caches

Await runtime request queue updates so limiter settings and auto-enabled
API key protections are recomputed when resilience settings change.

Preserve cancelled batch state for in-flight work by marking input files
processed without generating output artifacts, and replace cached synced
models with an empty set when remote discovery returns no models so the
providers route falls back to the local catalog instead of stale cache.

---------

Co-authored-by: congvc <congvc-dev@gmail.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
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.

2 participants