Move anthropic-beta header filtering from separate FilterBedrockBetaHeader
into ApplyBedrockCCCompat, so one function handles all CC compat processing
(body cleanup + header filtering). Change signature from ctx to *gin.Context
to access request headers. Remove the redundant separate call in handler.
- Remove `provider` and `metadata` fields from Bedrock request body.
Bedrock returns 400 ValidationException for unknown top-level fields.
- When filtered beta tokens list is empty, delete any pre-existing
`anthropic_beta` field in body to prevent client-injected tokens
from leaking to Bedrock unfiltered.
- Add `context-management-2025-06-27` and `fine-grained-tool-streaming-2025-05-14`
to bedrockSupportedBetaTokens whitelist per AWS documentation.
Service layer writes a complete JSON error response then returns error.
Handler's ensureForwardErrorResponse couldn't distinguish this from
"no response written" and appended an SSE event, corrupting the body.
Use gin.Context flag: service marks MarkResponseCommitted(c) after
writing, ensureForwardErrorResponse checks IsResponseCommitted(c)
and skips. Zero function signature changes, zero error wrapping.
When the gateway forces streaming to the upstream, the upstream returns
Content-Type: text/event-stream. In the non-streaming branches, the body
is correctly aggregated into JSON, but WriteFilteredHeaders has already
passed the upstream's text/event-stream header through. Since Gin's
c.JSON/c.Data only set Content-Type via writeContentType when it is not
already present, the SSE header is never overwritten -- so the body is JSON
while the header says SSE.
Downstream middlewares that decide "is this streaming?" by Content-Type
(e.g. new-api) then mis-handle the response as SSE, breaking non-streaming
JSON clients.
Fix: explicitly Set Content-Type to application/json right after
WriteFilteredHeaders in the three non-streaming JSON write paths.
- bump impersonated CLI version 2.1.92 -> 2.1.161; derive User-Agent from
CLICurrentVersion so the two hardcoded copies can no longer drift apart
- fix x-stainless headers to real 2.1.161 values: package-version
0.70.0 -> 0.94.0, runtime-version v24.13.0 -> v24.3.0 (verified against
the installed Bun-compiled binary)
- expand the disguise-path system prompt from a 2-block identity skeleton
to a 3-block layout (billing + identity + tool-agnostic prose), matching
real CC's multi-block shape; cache breakpoint moved to the last static
block. Deliberately excludes # Doing tasks / # Using your tools /
# Executing actions to avoid polluting proxied-client behavior
- stabilize the synthesized metadata.user_id session_id across conversation
turns: derive it from (account + client discriminator + first user
message) instead of a per-turn content/body hash. Sticky-routing
GenerateSessionHash is intentionally left untouched; remove now-dead
hashBodyForSessionSeed
Tests: update the 3-block system assertions in gateway_prompt_test and
gateway_anthropic_apikey_passthrough_test; add a session_id cross-turn
stability test in gateway_oauth_metadata_test.
Three periodic background jobs ran on every instance with no cross-instance
coordination, multiplying their cost (and side effects) by the replica count:
- DashboardAggregationService.runScheduledAggregation: N× heavy GROUP BY
aggregation queries every minute plus watermark write races.
- PaymentOrderExpiryService.runOnce: N× upstream payment-provider reconcile/
expiry API calls per pending order.
- SubscriptionExpiryService.sendExpiryReminders: N× full active-subscription
scans every minute and potential duplicate reminder emails.
Add a LeaderLockCache abstraction so only one instance runs each job per cycle:
- The interface lives in the service layer; the Redis-backed implementation
(SetNX + compare-and-delete release) lives in the repository layer, so the
service package keeps its depguard "must not import redis" boundary intact.
- tryAcquireSingletonLeaderLock prefers the cache and falls back to a Postgres
advisory lock when Redis errors, mirroring the Ops background services. When
neither backend is configured the job runs ungated, preserving single-instance
and test behavior (no self-lockout: the lock is released every cycle).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>