fix(openai): reacquire passthrough turn slots

This commit is contained in:
wucm667
2026-09-01 08:13:29 +08:00
parent 200602b41b
commit 5fb5dfb34c
4 changed files with 105 additions and 53 deletions
@@ -1616,10 +1616,10 @@ const (
// 由 BeforeTurn 在每个 turn 开始时冻结,AfterTurn 的用量提交读取它;turn 在
// 连接内串行推进,互斥锁只为跨用量提交 goroutine 的读取安全。
//
// ws_v2 passthrough ingress 没有 BeforeTurn,因此本值会保持零;AfterTurn 必须
// 以 TurnStarted 已记录的所属 turn 开始时刻为回退,而不是用建连或记录时刻。
// 这样每个 passthrough turn 都按自己的开始时刻计价,但不改变其仅在建连时执行
// 准入门、没有 turn 级利润复核的既有行为。
// 零值语义(重要):首轮准入由握手路径完成,不调用 BeforeTurn,因此首轮保持
// 零值并回退到 TurnStarted 记录的首轮开始时刻。后续 turn 在 response.create
// 写入上游前调用 BeforeTurn,按当时的利润门复核并冻结定价。绝不能用建连时刻
// 初始化,否则会把长连接的所有 turn 钉死在建连时的峰谷因子。
type openAIWSTurnPricing struct {
mu sync.Mutex
at time.Time
@@ -2321,8 +2321,8 @@ func (h *OpenAIGatewayHandler) ResponsesWebSocket(c *gin.Context) {
// turn-tagged slot preserves the exact mapping used for the in-flight request.
var turnChannelMapping atomic.Pointer[openAIWSTurnChannelMappingSnapshot]
turnChannelMapping.Store(&openAIWSTurnChannelMappingSnapshot{turn: 1, mapping: channelMappingWS})
// turn 级定价:BeforeTurn 重新冻结 pricingAt 并按最新门复核当前账号;
// passthrough 没有 BeforeTurn 时,AfterTurn 回退到 TurnStarted 的所属 turn 时刻。
// turn 级定价:首轮回退到 TurnStarted 的所属 turn 时刻;后续 turn 由
// BeforeTurn 重新冻结 pricingAt 并按最新门复核当前账号。
var turnPricing openAIWSTurnPricing
hooks := &service.OpenAIWSIngressHooks{
ClientLifecycleContext: clientLifecycleCtx,
@@ -2335,10 +2335,9 @@ func (h *OpenAIGatewayHandler) ResponsesWebSocket(c *gin.Context) {
c.Set(securityAuditWSTurnContextKey, turn)
service.BeginOpsStreamTurn(c, turn)
setCyberTurnBody(turn, payload)
// Passthrough ingress intentionally skips BeforeTurn, so enforce only
// the connection-level cyber session gate here as well. Native ingress
// visits this hook first and gets the same side-effect-free close error;
// its original BeforeTurn guard remains as defense in depth.
// 连接级 cyber session gate 也在 BeforeRequest 先执行,使 native 与
// passthrough ingress 都能在 BeforeTurn 及上游写入前无副作用地拒绝。
// BeforeTurn 中保留同一检查作为防御式兜底。
if cyberBlockedThisConn {
return service.NewOpenAIWSClientCloseError(coderws.StatusPolicyViolation, cyberSessionBlockedClientMsg, nil)
}