Repository navigation
v1.22.0 — Streaming lifecycle, tenant-safe caching and hook isolation
Added
- Managed streaming:
generateManagedResponseStream()exposes terminal outcome, emitted character count and settled usage. Its completion promise can be passed toctx.waitUntil(). - Client-local resilience state (#111, compatible phase): use
stateScope: 'client'or share an explicitResilienceContextacross clients and direct adapters. - Explicit
normalizeCanonicalRequest()andnormalizeLegacyRequest()boundaries preserve identity without inferring an ambiguous request shape (#113, first phase). llm.getRoutingInfo(request)/LLMProviderFactory.getRoutingInfo(request)(new): a preflight snapshot from the client's own dispatch planner and admission state. Shared model IDs resolve against configured hosts. Throws the newNoRouteError(codeNO_ROUTE, aConfigurationError) when no configured provider can serve the request.- Exported types
ManagedTextStreamandStreamCompletion;RequestEndEvent.outcome('success' | 'error' | 'cancelled'). responseCacheNamespacefactory option to separate factory response-cache keys per application or policy revision.- Per-provider
enableCircuitBreakeronProviderConfig;stateScopeandresilienceContextonfromEnvoverrides. CircuitBreaker.beginExecution()for callers that settle an execution later (used by streaming health).CacheObservability.factory.usageKind('historical'on hits) andcurrentChargeUsd(0 on hits).streamBodyTimeoutprovider option: opt-in total deadline for a streaming response, including its body.GatewayRoutePlan.executable(optional field, always set bygetGatewayRoutePlan()).- Factory
onCacheemits abypassevent when the response cache is skipped, including when a request identity cannot be hashed (for example aDateinmetadata).
Changed
- Factory response-cache keys are now versioned (v2), so entries written by 1.21 are not reused after upgrading.
cache.strategy: 'off'and'provider-prefix'now also bypass the factory response cache. - Legacy request normalization joins every system message with blank lines (previously only the first was kept) unless
systemPromptis set. - Local quota-hook denials throw
QuotaDeniedError, a subclass ofQuotaExceededErrorwith the sameQUOTA_EXCEEDEDcode, so existing checks keep matching. Denials no longer mark the provider exhausted or trigger fallback. - Catalog
getRoutingInfo()prefers a configured host for shared model IDs, but still returns a result (the catalog host) when no configured provider matches. - Streaming
onRequestEndis no longer success-only. Once output has started it fires on success, error and cancellation, withoutcomeandfinishReasonset to'error'or'cancelled'for failures, including plaingenerateResponseStream()calls. A provider that fails before any output emitsonRequestEnd(outcome: 'error') only if it reported real usage; otherwise onlyonRequestErrorfires. With fallback, one logical request can therefore produce oneonRequestEndper failed provider that reported usage, plus one for the provider that served it. Filter onoutcome === 'success'to count successes, and useonRequestErrorto count failures. - Factory streams are now demand-driven. 1.21 read the whole upstream stream as soon as it opened, so cost, quota and
onRequestEndwere recorded even if the caller never read it. A stream that is dropped without being read to the end or cancelled is now never settled or accounted. Read it to completion or callcancel(), and usegenerateManagedResponseStream()withctx.waitUntil(completion)where accounting must complete. - An explicit factory
enableCircuitBreaker: falsenow also turns off the adapters' own breakers (1.21 left them running). A per-providerenableCircuitBreakertakes precedence over the factory value; leaving both unset keeps 1.21 behavior. - Breakers no longer count caller or account errors as provider-health failures: authentication, invalid request, model not found, token limit, content policy, configuration, quota, and tool-loop abort/limit errors.
Documentation
- Updated public API reference, cache-layer policy, gateway diagnostic limitations, managed stream accounting and client-state/retry semantics. Clarified that KV snapshots and ledger monetary budgets do not provide atomic admission.
- Added the issue sweep checkpoint with implementation/remaining scope, validation evidence, release implications and GitHub progress-comment links. Historical issue exports are labeled separately.
Fixed
- Lifecycle hooks cannot replay requests (#116): the factory and directly constructed adapters now route every hook through a guard, so a throwing or rejecting
onRequestEnd(or any other hook) no longer triggers fallback to a second provider after a successful vendor call, usage/quota recording still runs, and async hooks cannot raise unhandled rejections. Each failing hook method is logged once through the configuredlogger. composeHookscontains async rejections and now calls each handler with its implementation asthis. Class-method handlers previously ran withthisundefined and failed silently; they now run. Handlers are still captured when composing.- Adapters built by the factory (for example from
getProvider()) and adapters givenhooksnow hold a guarded view of the caller's hooks object rather than the object itself. It exposes the same methods, resolved at call time, forwards writes, deletes and the prototype to the caller's object, and cannot itself be frozen (freeze the caller's object instead). - Streaming transport/lifecycle (#110): all adapters share incremental byte SSE framing, honor downstream demand and cancellation, settle partial usage on error, and apply a body deadline. HTTP cancellation aborts pending fetches. Factory accounting is awaited and exposed on every terminal path; streaming health now updates breakers at completion.
- Breaker policy (#111): disabled breakers bypass adapter execution; rejected calls cannot indefinitely postpone recovery; caller, authentication, local policy and account-quota errors do not degrade provider service health.
- Tenant-safe factory response caching (#108): versioned SHA-256 identities include semantic inputs, tenant, namespace, provider options, and routing policy. Cache bypass strategies are honored, invalid entries are ignored, and hits identify historical usage and check current quota policy.
- Local quota denial (#109, first patch):
QuotaDeniedErrorterminates regular, streaming, and image requests without exhausting a provider or applying fallback rules. Atomic spend reservations remain future work. - Published JavaScript sourcemaps (#94) embed original TypeScript source via
inlineSources.
Pull requests
- feat: streaming lifecycle, tenant-safe caching, client-scoped resilience (#119)
- fix: isolate lifecycle hooks so telemetry cannot replay requests (#120)
- chore: release v1.22.0 (#121)
Full Changelog: v1.21.0...v1.22.0