Commit graph

46618 commits

Author SHA1 Message Date
mateo-berri
0e999e32c9 test: fix staging CI regressions from #38182, #38144, #38265, #37962, and #37969
- test_custom_callback_input: audio redaction assertion expects None content
  (redaction leaves None untouched, gpt-audio-1.5 returns content=None)
- local_testing conftest: drain GLOBAL_LOGGING_WORKER in isolate_litellm_state
  teardown so mocked-router tests stop leaking pending logging tasks into
  test_gcs_pub_sub
- test_together_ai: tools is always a supported param now; only response_format
  is gated by function-calling support
- test_keys: /team/new omits models instead of sending null (422), so the key's
  team really exists and auth no longer raises TeamNotFoundError
- test_team_delete_member_add_race: per-test unique team and user ids so xdist
  workers sharing one Postgres stop deleting each other's team mid-race
2026-08-25 23:01:20 -07:00
Yuneng Jiang
d2aea2d4e7
refactor(ui): re-pull label, textarea, separator and skeleton from the registry
These four primitives still wrapped their body in React.forwardRef, which
the dashboard has not needed since it moved to React 19: a function
component receives ref as an ordinary prop and the existing {...props}
spread already hands it to the DOM node.

Re-pulling each from base-vega drops the wrapper and its displayName.
These four were picked because the ref plumbing is their only divergence
from current upstream, so the class strings, data-slot values and exports
are untouched and nothing renders differently. The other seven primitives
that still carry forwardRef have also drifted on their class strings, so
re-pulling them would ship a visual change alongside the cleanup and they
are left alone here.

Textarea is the one with real ref call sites, roughly seventeen of them
through react-hook-form's field.ref, and ref-forwarding.test.tsx did not
cover it. Add that case next to the Label, Separator and Skeleton ones
already there.
2026-08-25 22:56:59 -07:00
yuneng-jiang
e300822483
Merge pull request #38300 from BerriAI/litellm_/notion-docs-search-1e4443
refactor(ui): install the shadcn alert primitive
2026-08-25 22:47:55 -07:00
yuneng-jiang
6e569ee0c7
Merge pull request #38293 from BerriAI/litellm_internal_staging
Some checks failed
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
Unit Tests: Documentation Validation / documentation (push) Has been cancelled
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests / caching-local (push) Has been cancelled
Unit Tests / core-utils (push) Has been cancelled
Unit Tests / enterprise-routing (push) Has been cancelled
Unit Tests / integrations (push) Has been cancelled
Unit Tests / All Other Providers (push) Has been cancelled
Unit Tests / Vertex AI (push) Has been cancelled
Unit Tests / misc (push) Has been cancelled
Unit Tests / proxy-auth (push) Has been cancelled
Unit Tests / proxy-endpoints (push) Has been cancelled
Unit Tests / proxy-extras (push) Has been cancelled
Unit Tests / proxy-infra (push) Has been cancelled
Unit Tests / proxy-server (push) Has been cancelled
Unit Tests / responses-caching-types (push) Has been cancelled
GitHub Actions Security Analysis / zizmor (push) Has been cancelled
chore(ci): promote internal staging to main
2026-08-25 21:25:51 -07:00
Yuneng Jiang
a5b6f31585
refactor(ui): install the shadcn alert primitive
components/shared/Alert.tsx was base-vega's own alert.tsx copied in by
hand, carrying the same four exports and the same class strings, so
npx shadcn add could never reach it and it would drift from every
upstream fix silently. It also still wrapped each part in forwardRef,
which React 19 no longer needs.

Install the primitive into components/ui/ where the CLI can update it,
and reduce the shared file to a wrapper that adds the four status
variants (info, success, warning, error) the dashboard actually uses on
top of upstream's default and destructive.

Rendered output is unchanged: every variant produces byte-identical
classes, role and data-variant, so all 45 call sites look the same.
2026-08-25 21:24:07 -07:00
Mateo Wang
3e2927de9a
Merge pull request #38296 from BerriAI/litellm_fix_otel_provider_error_stack_trace
Some checks are pending
Postgres Tests / proxy-behavior (push) Waiting to run
Unit Tests: Documentation Validation / documentation (push) Waiting to run
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Waiting to run
Unit Tests: Proxy DB Operations / auth-checks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / budgets (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / custom-logging (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / db-and-spend (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / key-generation (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / logging-misc (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-server-core (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-utils (push) Blocked by required conditions
Unit Tests / integrations (push) Waiting to run
Unit Tests / All Other Providers (push) Waiting to run
Unit Tests / Vertex AI (push) Waiting to run
Unit Tests / misc (push) Waiting to run
Unit Tests / caching-local (push) Waiting to run
Unit Tests / core-utils (push) Waiting to run
Unit Tests / enterprise-package (push) Waiting to run
Unit Tests / enterprise-routing (push) Waiting to run
Unit Tests / proxy-auth (push) Waiting to run
Unit Tests / proxy-endpoints (push) Waiting to run
Unit Tests / proxy-extras (push) Waiting to run
Unit Tests / proxy-infra (push) Waiting to run
Unit Tests / proxy-server (push) Waiting to run
Unit Tests / responses-caching-types (push) Waiting to run
GitHub Actions Security Analysis / zizmor (push) Waiting to run
Unit Tests: Proxy DB Operations / proxy-runtime (push) Blocked by required conditions
fix(logging): keep tracebacks for provider-originated 4xx errors
2026-08-25 21:15:14 -07:00
mateo-berri
54aebc5696 chore: drop stray bootstrap.log 2026-08-25 21:05:10 -07:00
mateo-berri
728b73d1b3 fix(proxy): strip only the authenticating secret on credential-less Vertex passthrough
PR #38114 dropped whichever header user_api_key_auth would read the caller's
key from, by precedence. Under custom_auth, JWT auth, or no master key that
header is the caller's own Google token, so the bring-your-own-credentials
Vertex branch answered 401 to every valid request.

A header value is now dropped only when it is the master key or when its
hash is the api_key that authenticated the request, so a Google token that
auth never consumed keeps flowing while a LiteLLM key still never reaches
Google.

test_passthrough_post_call_guardrails.py no longer plants a MagicMock
proxy_server module in sys.modules at import, which poisoned sibling tests
that read module globals at call time.
2026-08-25 21:04:27 -07:00
mateo-berri
514cae1b3d fix(logging): keep the proxy's own budget rejection an expected client error
The auth handler stamps the requested model's provider onto BudgetExceededError before logging it, which made a key-over-budget 429 look provider-originated and regain its traceback (and an OTel stack_trace) after the provider 4xx carve-out. Any exception whose unified rate-limit category names litellm's own limiter is now a proxy rejection, matching the HTTPException rule.
2026-08-25 20:51:55 -07:00
mateo-berri
a6e1708e6c fix(logging): keep proxy-side rate limits as expected client errors
ProxyRateLimitError derives from HTTPException but carries an llm_provider,
so it read as provider-originated and regained its traceback. Any
HTTPException is a proxy rejection regardless of llm_provider.
2026-08-25 20:30:44 -07:00
mateo-berri
ac6deec529 fix(logging): keep tracebacks for unmapped provider exceptions too
The /v1/messages route logs the provider's raw BaseLLMException, which carries
no llm_provider, so its 4xx still counted as an expected client error and lost
its traceback. Treat BaseLLMException as provider-originated as well.
2026-08-25 20:25:38 -07:00
Mateo Wang
273b01af6c
Merge pull request #38286 from BerriAI/litellm_together_e2e_coverage
test(e2e): cover Together AI reasoning, tool calls, template kwargs, and cost through a live proxy
2026-08-25 20:16:31 -07:00
mateo-berri
68f5cbd522 fix(logging): keep tracebacks for provider-originated 4xx errors
is_expected_client_error treated every HTTP 4xx as a rejection the proxy
issued itself, so a 401 or 429 the provider returned lost its traceback in
the standard logging payload and the OTel error span dropped
litellm.provider.error.stack_trace. An exception carrying llm_provider is
an upstream or deployment problem and keeps its traceback; the proxy's own
pre-call rejections still skip it
2026-08-25 20:09:31 -07:00
Daniel Meismer
c74a8df52d chore(mcp): satisfy test quality lint
Document the intentional internal seams used by the DCR bridge admission tests and normalize import ordering.\n\nGenerated with AI\n\nCo-Authored-By: Codex
2026-08-25 22:48:22 -04:00
yuneng-jiang
72b8b47ee8
bump: litellm-enterprise 0.1.59 -> 0.1.60, litellm 1.99.0 -> 1.100.0 (#38243) 2026-08-25 19:44:45 -07:00
Daniel Meismer
1a2a24ecc6 chore(mcp): document mutable bridge header shape
Generated with AI

Co-Authored-By: Codex
2026-08-25 22:40:01 -04:00
Daniel Meismer
da036ad0f0 fix(mcp): harden DCR bridge admission
Preserve standard Authorization key validation while preventing client MCP credentials from receiving anonymous bridge admission.

Generated with AI

Co-Authored-By: Codex
2026-08-25 22:33:07 -04:00
Daniel Meismer
a66e091cd7 fix(mcp): complete DCR bridge OAuth challenges 2026-08-25 22:33:07 -04:00
Devin AI
5c8852c0d6 fix: support image_edit health checks
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-26 02:14:44 +00:00
Deepanshu Lulla
d16c4dad4b
feat(logging): add async_post_call_failure_deployment_hook (#36657)
* feat(logging): add async_post_call_failure_deployment_hook

CustomLogger already has async_pre_call_deployment_hook and
async_post_call_success_deployment_hook, both firing once per real
deployment attempt from wrapper_async since the router re-enters that
wrapper fresh on every retry and fallback step. There was no failure-side
counterpart; the only failure signal, async_log_failure_event, fires once
per logical client request behind a dedup gate, so fallback chain attempts
2+ were invisible to callbacks needing per-deployment-attempt granularity.

Adds async_post_call_failure_deployment_hook(request_data, exception,
call_type) to CustomLogger and a matching dispatcher in utils.py, called
from wrapper_async's except block. It needs no dedup coordination since
each real attempt naturally re-enters the wrapper once. Unlike its two
siblings, the dispatcher wraps each callback call in its own try/except
since it runs on the wrapper's own exception path and a broken callback
must never mask the exception about to be re-raised to the caller.

* feat(logging): pass fallback_depth through to async_post_call_failure_deployment_hook

Router already tracks fallback_depth internally on each fallback hop
(litellm/router_utils/fallback_event_handlers.py), incrementing it once per
target tried, but nothing surfaced it to CustomLogger callbacks. Reads it
off request_data in the dispatcher and passes it through as a best-effort
int | None keyword: None on the first, pre-fallback attempt or a bare SDK
call with no router, 1 on the first fallback hop, 2 on the second, and so
on. Verified live against a real multi-hop Router fallback chain before
adding the regression tests.

* fix(logging): fire async_post_call_failure_deployment_hook on internal calls too

The failure hook was gated behind the same not _is_litellm_internal_call
check as the request-level dedup-gated failure logging, so a failed
internal sub-call (e.g. an emulated file-search step) never reached it,
even though its async_pre_call_deployment_hook and
async_post_call_success_deployment_hook siblings already fire
unconditionally for such calls.

* chore: retrigger CI (lint job hit a transient GitHub Actions infra outage on the prior push)

* chore: retrigger CI (lint job hit the same GitHub Actions infra outage again)

* fix(logging): scope async_post_call_failure_deployment_hook to the actual model call

The hook was dispatched from the wrapper's broad outer except, which also
catches BudgetExceededError (raised before any deployment attempt),
errors from async_pre_call_deployment_hook, and errors raised after a
successful model call (post_call_processing, async_post_call_success_deployment_hook,
caching). None of those are a deployment attempt failing, so the hook
misreported them as one.

Scoped the hook to a try/except around the model call itself, so it only
fires when that specific call raises, matching its own documented contract.

* test: assert the callback actually ran in the failure-hook error-isolation test

An upstream test-quality gate (TQ001) flagged this test for asserting
nothing, so it could only fail by raising. Track whether the exploding
callback actually ran and assert on it, so the test would catch a
dispatcher that silently skipped every callback instead of isolating a
raising one.

* fix(logging): harden async_post_call_failure_deployment_hook against 5 maintainer-verified issues

A maintainer's live-proxy A/B review against base found five real
problems with the failure hook, all reproduced and fixed:

- The dispatcher called overrides with fallback_depth as a required
  keyword, so an override matching this PR's own earlier 3-arg
  proof-of-fix example raised TypeError, swallowed at debug level, on
  every call. Now checks the override's signature once per class and
  omits the keyword when unsupported.
- A callback mutating the exception it receives (e.g. status_code)
  changed what the real caller got back, since it was the same live
  object about to be re-raised. Callbacks now receive a same-class
  snapshot instead.
- request_data exposed attempted_targets, the router's own live
  fallback-walk bookkeeping shared by reference across every hop, so a
  callback calling .record() on it could make the router skip a
  deployment it never actually tried. Now excluded from what the hook
  receives.
- The hook's own await sat directly in the model-call except block, so
  a caller-side cancellation landing mid-await (e.g. asyncio.wait_for)
  replaced the real deployment exception with CancelledError/
  TimeoutError. Now isolated so hook dispatch can never mask the real
  failure.
- The timestamp used for the reported failure duration was captured
  after the hook ran, so a slow callback inflated
  async_log_failure_event's duration. Now captured before the hook
  dispatches.

* fix(logging): preserve traceback/cause/context on the failure-hook exception snapshot

Bugbot found a real gap in the previous round's exception-mutation fix:
_snapshot_exception_for_hook only copied __dict__ and args, so a
callback formatting or inspecting the failure chain saw an empty
traceback and lost chained-exception context, even though the live
exception still has them. __traceback__/__cause__/__context__ aren't
stored in __dict__, so they need copying explicitly.

* fix(logging): preserve __suppress_context__ on the failure-hook exception snapshot

Setting __cause__ has a documented CPython side effect of implicitly
forcing __suppress_context__ to True. Since the previous round's
traceback fix set __cause__ before __suppress_context__, a normal
implicit-chaining exception (no `raise ... from`, __suppress_context__
naturally False) got its context wrongly suppressed on the snapshot.
Now __suppress_context__ is set explicitly, after __cause__, so it
always reflects the real exception.

* fix(logging): use MappingProxyType for the failure-hook's sanitized request_data

A LIT002 budget check (surfaced by rebasing onto a moved base) flagged
the dict comprehension building safe_request_data as mutable
construction. MappingProxyType is also a strictly better fit here: a
genuinely read-only view, not just an immutable-looking dict, matching
the intent that callbacks should never be able to mutate what they're
handed.

---------

Co-authored-by: Deepanshu <deepanshu.lulla@alpha-sense.com>
2026-08-25 19:01:03 -07:00
Devin AI
ed9575520b fix(azure): build v1 image query params without a mutable dict
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-26 01:55:57 +00:00
mateo-berri
893482d4ac test(e2e): drop docstrings on the cost map model and its client accessor 2026-08-25 18:51:40 -07:00
Devin AI
1cf683c980 fix(azure): honor base_url client param and drop stale api-version on v1 image routes
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-26 01:49:48 +00:00
mateo-berri
55e3a9785c test(e2e): trim docstrings that restate the Together tests 2026-08-25 18:45:52 -07:00
Devin AI
5802cf0d8d fix(azure): drop deployment path from api_base when building v1 image routes
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-26 01:40:59 +00:00
Devin AI
a6e68b1b0e fix(azure): wrap v1 image route litellm_params in MappingProxyType
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-26 01:29:57 +00:00
Devin AI
80fd9970c2 fix(azure): route image generation and edits through /openai/v1 for v1 api versions
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-26 01:25:26 +00:00
mateo-berri
cc6b3783fe test(e2e): price Together cached tokens at the registry cache read rate 2026-08-25 18:17:28 -07:00
mateo-berri
a543b0348c test(e2e): cover Together AI reasoning, tool calls, template kwargs, and cost through a live proxy 2026-08-25 18:12:33 -07:00
mateo-berri
a2c8ba7b5d Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_together_structured_outputs 2026-08-25 18:05:53 -07:00
Mateo Wang
ea6ac3dd99
Merge pull request #38283 from BerriAI/litellm_together_regression_tests
test(together_ai): regression suite across chat, responses, and messages surfaces
2026-08-25 17:58:14 -07:00
Mateo Wang
d0c527f3bb
Merge pull request #36245 from BerriAI/litellm_fix_headroom_stream_leak
fix(anthropic): buffer streamed responses carrying server-fulfilled tools so retrieval tool calls never reach the client
2026-08-25 17:48:31 -07:00
mateo-berri
602bf1c0ac test(together_ai): close the injected async client after the streaming test 2026-08-25 17:45:54 -07:00
mateo-berri
7c3da429c7 test(together_ai): regression suite across chat, responses, and messages surfaces
Adds streaming, async, /v1/responses, and /v1/messages coverage for the
Together AI overhaul (#38233, #38248, #38230, #38265, #38275), plus the
legacy api.together.xyz host and TOGETHER_AI_API_BASE through
litellm.completion. Each new test fails under a one-line mutation of the
merged code.
2026-08-25 17:40:41 -07:00
ryan-crabbe-berri
89d2d34453 fix(ui): lint z-index utilities behind arbitrary Tailwind variants
The variant prefix pattern only understood word variants, so
data-[side=top]:z-50 or [&>*]:z-[5] slipped past the rule. Parse the
utility as everything after the last top-level colon (brackets and
parens nest) and also strip the important marker. Formats the two files
prettier flagged in CI
2026-08-25 17:34:45 -07:00
mateo-berri
8d877980e0 fix(caching): carry has_buffered_provider_output through the anthropic stream cache writer 2026-08-25 17:31:46 -07:00
Mateo Wang
99e1eaaca0
Merge pull request #38205 from BerriAI/litellm_decrease_anys_opus5_round2
refactor(repositories): type prisma table access with one generic protocol
2026-08-25 17:19:57 -07:00
mateo-berri
599905356f fix(together_ai): pass reasoning_effort=high through on DeepSeek-V4-Pro 2026-08-25 17:17:47 -07:00
mateo-berri
abe9af622b fix(cost): keep size buckets for Together registry rows without pricing 2026-08-25 17:09:12 -07:00
Matthew Lapointe
418012aac5 fix(bedrock): type GPT-5 reasoning field and update capability test
Type the GPT-5.x reasoning payload with a ReadOnly TypedDict so the dict
literal satisfies the type-discipline budget, and drop the now-redundant
thinking pop (the thinking mapping is already skipped for these models).
Update the cross-region capability test to expect reasoning_effort offered
and thinking/output_config withheld for GPT-5.x on Converse.
2026-08-25 20:06:53 -04:00
ryan-crabbe-berri
6385b6d801 refactor(ui): replace hand-picked z-index values with one named scale and lint it
Regression LIT-6143 (the policy Flow Builder painting its guardrail dropdown
underneath a position: fixed shell at z-index 1000) was one instance of a
class of bug: pages picking their own z-index numbers above the portalled
popup layer. This removes the class.

- globals.css defines the only z-index values in the dashboard as Tailwind
  utilities: z-raised, z-chrome, z-sticky, z-sticky-pinned, z-floating,
  z-overlay, z-popup; every numeric, arbitrary and inline z-index across
  src is migrated onto them and tailwind-merge learns the tokens
- new local/no-ad-hoc-z-index ESLint rule bans z-<n>, z-[...], z-(...) and
  inline zIndex everywhere, and reserves z-popup for the portalled
  primitives in components/ui (and the DataTable menus)
- the Flow Builder renders in the dashboard content area instead of as a
  fixed full-screen overlay, so it has no stacking level at all
- the guardrail content-filter Add keyword / Add pattern / Custom pattern
  dialogs drop the leftover z-[1100] (renamed from ABOVE_ANTD_MODAL when
  antd was removed) that hid their own Action select and pattern combobox
  behind the dialog, the same bug as LIT-6143
2026-08-25 17:02:35 -07:00
mateo-berri
64d8b24c10 fix(router): deliver the hold-back retrieval error to the client instead of falling back 2026-08-25 17:00:00 -07:00
mateo-berri
ac866e98c8 chore(cost): drop redundant together fallback comment 2026-08-25 16:56:17 -07:00
ryan-crabbe-berri
434add755a
Merge pull request #38274 from BerriAI/litellm_test_lint_os_environ
test: gate the test tree on B003 so a test cannot swap os.environ for a plain dict
2026-08-25 16:51:45 -07:00
mateo-berri
7965cfdd2b fix(proxy): keep listing a keyless user's own models when their user row is missing
Keys minted by /key/generate get no LiteLLM_UserTable row, so /v2/model/info?user_models_only=true for such a user hit the new None guard and returned 400 where the merge base returned the user's own models. Skip the team-model merge for a missing row instead of raising, since a user with no row belongs to no team
2026-08-25 16:51:03 -07:00
mateo-berri
98b04de455 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_decrease_anys_opus5_round2
# Conflicts:
#	litellm/responses/litellm_completion_transformation/session_handler.py
2026-08-25 16:48:41 -07:00
mateo-berri
6fafb46731 fix(cost): apply Together AI cache read pricing and per-model registry rates 2026-08-25 16:45:50 -07:00
ryan-crabbe-berri
50a42ba38e
Merge pull request #38273 from BerriAI/litellm_fix_flow_builder_guardrail_dropdown_zindex
fix(ui): stack policy flow builder below the popup layer so guardrail options render
2026-08-25 16:43:53 -07:00
mateo-berri
1aca5a93d4 Merge remote-tracking branch 'origin/litellm_fix_headroom_stream_leak' into litellm_fix_headroom_stream_leak
# Conflicts:
#	litellm/proxy/common_request_processing.py
#	tests/test_litellm/proxy/test_budget_reservation.py
2026-08-25 16:40:05 -07:00
mateo-berri
1b37397633 fix(router): forward the hold-back keepalive ping live and carry the withheld-output flag through the stream wrappers
The router's pre-content ping filter dropped AgenticAnthropicStreamingIterator's
hold-back keepalive, so a held-back turn sent the client nothing until the buffer
settled. A ping that no lifecycle frame precedes is now forwarded live, since a
fallback's message_start can still follow it without overlapping lifecycles

The proxy's cancel-refund guard checked isinstance against the iterator, but the
proxy only ever sees it behind FallbackAwareAnthropicMessagesStream and
AnthropicMessagesStreamingResponse, so a disconnect during hold-back refunded the
budget reservation anyway. Both wrappers now forward a duck-typed
has_buffered_provider_output flag, and the router wrapper follows a fallback
source so the flag tracks the stream actually being consumed
2026-08-25 16:39:14 -07:00