[pull] main from danny-avila:main - #156
Merged
Merged
Conversation
…ay (#369) * 🩹 fix: Keep Truncated Tool-Call Inputs JSON Objects on Anthropic Replay Fixes the intermittent `validate / summarization tests (anthropic)` CI failure: 400 `messages.N.content.M.tool_use.input: Input should be an object` in the cross-provider summarization test (~2/15 main runs). Root cause — not a model-side max_tokens truncation (the #280 OutputTruncationError guard works, verified live): under a tight summarization budget the pruner's fit-to-budget pass calls `preFlightTruncateToolCallInputs` with an effective budget of a few dozen tokens, so the per-input cap (~20 chars) falls below the `{_truncated, _originalChars}` envelope and `createBoundedTruncationValue` returned `null` — for BOTH the tool_use block's inline input (a raw partial-JSON string after streaming) and its `tool_calls[].args` mirror. The projection mutates the live state array, so the nulls persist into retained history; the fork's outbound sanitizer only handled *string* inputs, so the next Anthropic call shipped `"input": null` and the API rejected it. (The escaped string serializes ~7 chars longer than the args object, which is why args sometimes survived while the block nulled — those replays self-healed via the tool_calls restore path, hence the flakiness.) Fix, both ends: - `createBoundedTruncationValue` returns `{}` instead of `null` when even the empty envelope overflows the cap, so state never carries a non-object input (also upgrades `serializeToolCallInput`'s degenerate output from `'null'` to `'{}'` on the OpenAI arguments path). - `_convertMessagesToAnthropicPayload` now coerces every tool_use/server_tool_use input to a JSON object after all restore paths run (`coerceAnthropicToolUseInput`): strings parse when they form complete JSON objects, everything else degrades to `{}` — covering null, arrays, and strings that parse to non-objects, on both the content-block and the materialized `tool_calls` branches. Reproduced live by intercepting `createStreamWithRetry` payloads in a 12-turn descending-budget loop; post-fix the same loop and the real cross-provider spec pass with zero bad payloads. The co-occurring `final_context_overflow` failure mode is a separate tight-budget calibration issue (instructions + summary consume the whole descending budget) and is intentionally not addressed here. * 🩹 fix: Address Review — Restore Intact Args Past `{}` Inputs, Coerce Server-Tool Branch Two review findings on the replay hardening: - The restore-from-`tool_calls` branch only fired on `''`/nullish inline inputs, so the asymmetric-truncation window (inline string degraded to `{}` while the smaller args object survived) shipped `{}` despite the real args sitting on the message. Treat an empty plain object as restorable too — a genuinely empty call's args mirror is also `{}`, so preferring the mirror never loses information. - The early `srvtoolu_` normalization branch returned before the new coercion and used a bare `JSON.parse`, so a string input parsing to a non-object (`'123'`, `'[1,2]'`) shipped as-is and would 400. Route it through `coerceAnthropicToolUseInput` like every other tool_use path. * 🧪 test: Type the Replay Regression Suite Against SDK Block Params Codex review: the new suite's fixtures and extracted blocks were `any`-typed, removing compiler checking from the assertions that guard the Anthropic payload shape. Fixtures now build from a narrow `DegradedToolInput` union (the shapes context-pressure truncation can actually leave in state) and assertions read `AnthropicToolUseBlockParam`/`AnthropicServerToolUseBlockParam` blocks through a typed finder.
…vergence Circuit Breaker (#368) * 🔁 fix: Verify Eager Prestart Args Against Canonical Accumulation + Divergence Circuit Breaker Fixes the eager-args divergence loop (danny-avila/LibreChat#14371): for tools with large repetitive arguments (SQL/code), the eager prestart accumulator's lossy heuristics (repeat-fragment dedupe, overlap merge) silently dropped legitimate payload fragments. The prestarted execution then mismatched the final request materialized from LangChain's canonical tool_call_chunks concatenation, ToolNode's "changed after eager execution started" guard errored, and every model retry re-prestarted and re-diverged until the run consumed the recursion limit — while the tool had already executed with args the model never requested. Two-part fix: 1. Canonical seal verification: the accumulator now also tracks rawArgsText, the verbatim in-order concatenation identical to AIMessageChunk.concat. getStreamedReadyToolCalls only prestarts when the heuristic view is confirmed to match it; otherwise the call falls through to normal ToolNode execution with final args. Explicit adapter seals that restate the finished call's full args (OpenAI Responses function_call_arguments.done) are honored as authoritative, since plain concatenation intentionally differs there. 2. Run-scoped circuit breaker: if the changed-args guard still fires, ToolNode records the tool name in eagerEventToolSuppressions (shared by reference with the stream handler), which stops prestarting that tool for the rest of the run — the retry executes normally and the loop is structurally impossible. Regression tests reproduce both divergence modes (overlap collision, repeat dedupe, combined), assert clean streams still prestart, and drive the previously-looping retry sequence to normal execution with canonical args on every round. * 🦺 fix: Restrict Seal Restatement Trust to Chunks That Supply Args (Codex P1) A pure-signal adapter seal (Bedrock contentBlockStop, args:'') preserved lastArgsFragment, so a repeated complete-JSON fragment could leave lastArgsFragment === argsText while the canonical concatenation differed — incorrectly blessing the reconciled args as an authoritative restatement and bypassing the rawArgsText comparison. Track sealedArgsFragment instead: only a non-empty args fragment carried by the chunk whose explicit seal covers the call (OpenAI Responses arguments.done) can qualify as a restatement. Regression tests cover the Bedrock pure-signal seal (no prestart) and the OpenAI Responses restatement contract (still prestarts). * ⚡ fix: Length-Based Canonical Verification + Identity-Mismatch Suppression (Codex P2s) 1. rawArgsText retained every cumulative prefix (quadratic growth for restating streams) and parsed the full concatenation at seal time. Replace it with rawArgsLength: every reconciliation branch yields text no longer than the plain concat, with equality exactly when every merge was a pure append — so argsText.length === rawArgsLength proves argsText IS the canonical accumulation. O(1) memory, no seal-time parse of raw text. 2. The changed-args guard now suppresses execution.toolName in addition to request.name, so a deterministic streamed-name-A/final-name-B divergence cannot keep prestarting A (and repeating its side effects) on every retry.
…tter (#370) * 🩹 fix: Coerce Non-Object toolUse Inputs in the Bedrock Converse Formatter Bedrock twin of the Anthropic replay hardening in #369. Conversations persisted before that fix can still carry tool calls whose inline `input` and `tool_calls[].args` were truncated to `null` by the pruner's old envelope-overflow branch, and Anthropic-shaped inline blocks keep the raw streamed JSON string — Converse rejects a non-object `toolUse.input`, and one formatter path threw before the request was even built. - `convertAIMessageToConverseMessage`: an unmirrored inline tool_use block with a non-object input no longer throws `Invalid Anthropic tool_use content block` — a complete JSON-object string parses, everything else degrades to `{}`. Blocks missing their id or name still throw (nothing to build a toolUse from). Tool calls materialized from `tool_calls` coerce their args the same way. - `convertFromV1ToChatBedrockConverseMessage`: same coercion on the v1 `tool_call` block path and the `tool_calls` fallback. `coerceBedrockToolUseInput` is a deliberate duplicate of the Anthropic fork's `coerceAnthropicToolUseInput` so each fork stays self-contained against its upstream. * 📝 chore: Drop Narrating Coercion Comment (Codex review) The helper's JSDoc already documents why non-object inputs coerce; the inline restatement violated the AGENTS.md comment policy.
The release job matched the head commit subject against an exact `^vX.Y.Z$`, so squash-merged version PRs — whose subjects carry a " (#123)" suffix — were silently skipped (v3.3.6, v3.3.7, and v3.3.9 got no tag or GitHub release while direct pushes like v3.3.8 did). Accept the suffix and strip it back to the bare version.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )