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 a Forward implementation already wrote a complete non-SSE (JSON) error
response to the client and returned an error -- e.g. the case-400 passthrough
in GatewayService.handleErrorResponse -- the handler unconditionally called
ensureForwardErrorResponse, which detected the writer was already written and
appended a fallback `data: {"type":"error",...}` SSE frame. The client then
received a corrupted body: the upstream JSON immediately followed by a stray
`data:` line.
Add gatewayForwardErrorAlreadyCommunicated (and the OpenAI counterpart) to
detect this case -- writer size changed AND Content-Type is not
text/event-stream -- and skip the fallback. SSE streams that only flushed
keepalive pings or partial data still receive a protocol-compliant terminal
frame, so strict SDKs (Codex CLI) do not see a silent EOF.
Applied consistently across the Messages / ChatCompletions / Responses
gateway handlers and the OpenAI chat/images handlers. Added regression tests
covering JSON passthrough, mid-stream SSE 400, nil-error and no-write cases.
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.
PR #3016's merge appended a verbatim second copy of four
TestStream_Reasoning* functions into
chatcompletions_responses_stream_lifecycle_test.go, causing
'redeclared in this block' build failures that broke both the
test and golangci-lint CI jobs.
Remove the duplicate block; each test now appears once.
userRepository.Delete used the r.client.Tx(ctx)+ErrTxStarted pattern,
which only reuses a transaction when r.client is itself a tx-bound
client. With the singleton base client (how Wire injects repos),
r.client.Tx() always starts a NEW independent transaction and ignores
any tx carried in the context.
As a result, AdminService.DeleteUser's transaction (added in #3047)
only covered the API-key deletions; the user soft-delete escaped into a
separate, immediately-committed transaction. If the outer commit failed
after the user tx had committed, the user was deleted while its API keys
stayed active, reintroducing the unrecoverable state of #3021.
Add a leading dbent.TxFromContext check (mirroring DeleteWithAudit) so
Delete joins an externally-provided transaction and lets the caller
commit/rollback. Extract the deletion body into deleteUser(ctx, exec,
id). All three original paths are preserved; callers passing a plain ctx
are unaffected.
Add user_repo_delete_atomicity_integration_test.go: builds repos with
the base client and a manual outer tx, asserting that delete-keys and
delete-user roll back together on abort and commit together on success.