Skip to content

[pull] main from danny-avila:main - #162

Merged
pull[bot] merged 1 commit into
innFactory:mainfrom
danny-avila:main
Aug 6, 2026
Merged

[pull] main from danny-avila:main#162
pull[bot] merged 1 commit into
innFactory:mainfrom
danny-avila:main

Conversation

@pull

@pull pull Bot commented Aug 6, 2026

Copy link
Copy Markdown

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 : )

* feat: shared adaptive stream smoother engine

Bounded producer/consumer queue with backlog-proportional piece sizing:
piece = max(4, ceil(buffered * tick / 250ms)), sliced at dequeue time so
render lag stays pinned near the target latency regardless of provider
chunk size or reply length. Three-way item classification (splittable /
atomic / passthrough), first-token zero delay, cadenced sleeps, canonical
abort semantics, strict FIFO for unsmoothed metadata chunks.

* refactor: migrate OpenAI chat-model family onto the shared smoother

delayStreamChunks keeps its signature but delegates to smoothStream;
plain text splits adaptively, logprobs/finish_reason and reasoning-delta
chunks pace whole (atomic), tool-call/usage-only chunks pass through
undelayed in FIFO order. Defaults change from undefined to 25ms via
resolveStreamDelay; _lc_stream_delay: 0 disables.

* refactor: migrate Anthropic client onto the shared smoother

The bespoke bounded queue (which the engine generalizes) is replaced by a
producer generator yielding classified SmoothItems; extractToken/cloneChunk
stay as the provider adapter. Empty text-block starts are still skipped,
tool-input and usage/id-only chunks still bypass pacing in FIFO order, and
the early-break producer close is preserved via abortUpstream. Adaptive
sizing now bounds render lag on fast streams; default stays 25ms.

* refactor: migrate Bedrock Converse client onto the shared smoother

Delta pieces are built lazily via the exact per-piece pipeline (sliced
delta -> chunk -> enrichment -> stream-limit link) against an arrival-time
snapshot of seen block indices, so output is byte-identical to the legacy
splitter. toolUse seals, metadata and misc events pass through unpaced in
FIFO order (seal timing test added). Default changes 0 -> 25ms; explicit
0 disables (parameter-passthrough tests updated accordingly).

* feat: adaptive stream smoothing for Google and Vertex clients

Both clients previously streamed raw provider chunks with no pacing at
all. A generic ChatGenerationChunk adapter (shared with Mistral) splits
plain-text deltas — string content or google-common's single text-part
array shape — while thought/media/function parts pace whole and metadata
passes through unpaced. Raw generators no longer dispatch runManager
callbacks; dispatch moves per emitted piece so callback-echo consumers
observe smoothed deltas. _lc_stream_delay hoisted onto the shared
client-options surface (StreamSmoothingOptions) for every provider.

* feat: Mistral subclass with adaptive stream smoothing

CustomChatMistralAI wraps the parent stream with the generic smoothing
adapter, registered for both MISTRALAI and MISTRAL provider keys. The
Jest stub for @langchain/mistralai (ESM-only) becomes a minimal
functional double so the subclass is constructible under tests; honest
failure moves from the constructor to the network layer.

* feat: linear tail drain + end-to-end cadence benchmark

Once the producer finishes, remaining backlog drains linearly across the
target latency window instead of decaying geometrically — the measured
tail drops from ~3.2x to under 3x target. Benchmark drives the real
ChatOpenAI against a local SSE server emitting 110-char chunks at 20ms,
asserting even cadence, sub-10ms jitter, bounded lag, and that lag stays
flat as reply length quadruples.

* v3.4.0

* test: load-tolerant benchmark bounds; document logprobs predicate split

Absolute cadence/jitter/lag bounds loosen for parallel-worker and CI
scheduling noise; the discriminating flat-lag-with-length assertion stays
tight. Cross-reference comments pin why the OpenAI-family and generic
adapters intentionally differ on logprobs (DeepSeek contract vs
google-common's always-present empty logprobs).

* style: satisfy eslint — emit return types, dead guard removal, formatting

Explicit return types on SmoothItem emit arrows (anthropic/bedrock),
drop a provably-dead null guard on additional_kwargs, widen a runtime
type probe to unknown, and auto-fixed indentation/blank lines.

* fix: keep reasoning-bearing chunks atomic; scope split-piece metadata; sync lockfile

Addresses Codex review: chunks pairing visible text with reasoning
payloads in additional_kwargs now pace whole in both the generic and
OpenAI-family adapters — split pieces would each clone the kwargs and
the aggregator's dict merge concatenates string fields once per piece.
Generic clone functions now carry additional_kwargs/response_metadata/
usage_metadata on the first piece only (pinned by new adapter tests).
package-lock.json version synced to 3.4.0.

* fix: cover reasoning_details and camelCase finishReason; abort-aware idle wait; bounded teardown

Second Codex round: OpenRouter reasoning_details join the unified
reasoning-kwargs predicate (shared by both adapters); google-common's
camelCase finishReason now blocks splitting like finish_reason; a
consumer parked on an empty queue wakes on signal abort instead of
waiting for a provider that may ignore it; and generator teardown
force-closes the source and bounds the producer wait with a 1s grace so
a stalled stream can no longer block abort propagation indefinitely
(previously it could — proven by the new parked-abort test).

* style: drop unnecessary optional chain on Node timeout

* fix: third Codex round — lazy disabled path, admission-side text cap, tool/usage split guards

- delayMs <= 0 now takes a direct pass-through path: no background
  producer, no read-ahead — disabled smoothing is byte- and
  laziness-identical to the pre-engine behavior.
- Oversized splittable items are segmented at admission (4096-char word
  boundary segments), so a single giant provider chunk parks the
  producer at the text cap like the legacy split-before-enqueue queues;
  wrapped emit maps segment pieces back to chunk-global isFirst/isLast.
- Mixed text/tool-call deltas pace whole in both adapters
  (hasToolCallChunks) — split pieces would duplicate tool arguments.
- Generic clone functions scope generationInfo to the first piece
  (Vertex reports authoritative usage there; replication inflated
  per-piece trace observations). OpenAI-family clones keep per-piece
  generationInfo deliberately: token indices live there and scalar
  metadata dedup already handles repetition.

* fix: hard-cut boundary-free runs at a 64-char lookahead past target size

Fourth Codex round: findStreamChunkBoundary previously scanned to end of
string when no boundary char existed, letting base64/minified deltas
defeat both the admission segment cap and per-tick piece sizing. The
word-boundary search now extends at most 64 chars past the target before
hard-cutting — natural language is unaffected (boundaries land within a
few chars); boundary-free runs stay within budget. Pinned by unit and
engine tests.

* fix: fifth Codex round — cross-item tick batching, reasoning-only pacing, robustness guards

- P1: one cadence tick now drains up to the adaptive budget ACROSS queued
  items, so token-sized provider deltas coalesce instead of costing a full
  tick each (400 one-char deltas: ~25s -> under target window). Passthrough
  items flush free mid-batch in FIFO order; atomic items take their own tick.
- Reasoning-only deltas (Gemini thoughts, OpenRouter reasoning_details,
  reasoning summaries) now pace atomically via a shared
  getReasoningKwargsText extractor wired into both adapters, instead of
  passing through unsmoothed.
- resolveStreamDelay normalizes non-finite values to the default, and the
  engine's disabled-path guard inverts to catch NaN defensively.
- Producer failures are tracked with an explicit failed flag so nullish
  rejection values still propagate instead of truncating silently.

* fix: extend StreamSmoothingOptions to Azure and OpenRouter option types

Codex: typed factory usage for AZURE/OPENROUTER rejected
_lc_stream_delay even though both runtime classes read it with the new
25ms default. AzureClientOptions and the OPENROUTER ProviderOptionsMap
entry intersect StreamSmoothingOptions; ChatOpenRouterInput carries the
field inline (avoids a types<->openrouter import cycle). Compile-time
pin added to the smoke suite.

* fix: close the split-safety class — lossless-reassembly property test

Instead of another denylist entry, pin the invariant itself: for an
adversarial matrix of chunk shapes (unknown kwargs, scalar response
metadata, usage, reasoning variants, tool calls, google array shapes),
aggregating the emitted pieces must reproduce the original chunk's
observable payload. The property immediately caught three live gaps,
all now fixed:
- OpenAI-family clone replicated additional_kwargs/response_metadata on
  every split piece (unknown string fields concatenated downstream);
  now first-piece-only, matching the generic adapter. generationInfo
  deliberately stays per-piece (token indices + scalar dedup own it).
- Generic array-shape split required no merge key, so index-less parts
  aggregated as N separate content parts; splitting now requires a
  numeric part index, index-less parts pace whole.
- (Third failure was harness noise: concat() adds empty token-detail
  objects; usage asserts on token counts.)

* style: drop unused type import in reassembly property test
@pull pull Bot locked and limited conversation to collaborators Aug 6, 2026
@pull pull Bot added the ⤵️ pull label Aug 6, 2026
@pull
pull Bot merged commit d4e3598 into innFactory:main Aug 6, 2026
1 check passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant