| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342 |
- package openai_ws_v2
- import (
- "bytes"
- "context"
- "errors"
- "io"
- "net"
- "strconv"
- "strings"
- "sync"
- "sync/atomic"
- "time"
- coderws "github.com/coder/websocket"
- "github.com/tidwall/gjson"
- "github.com/Wei-Shaw/sub2api/internal/pkg/xai"
- )
- type FrameConn interface {
- ReadFrame(ctx context.Context) (coderws.MessageType, []byte, error)
- WriteFrame(ctx context.Context, msgType coderws.MessageType, payload []byte) error
- Close() error
- }
- type Usage struct {
- InputTokens int
- OutputTokens int
- CacheCreationInputTokens int
- CacheReadInputTokens int
- ImageOutputTokens int
- }
- type RelayResult struct {
- RequestModel string
- ResponseModel string
- ResponseModelConflict bool
- // ResponseServiceTier is the raw service_tier declared by the last terminal
- // response event; "" when the upstream never declared one.
- ResponseServiceTier string
- Usage Usage
- RequestID string
- TerminalEventType string
- FirstTokenMs *int
- Duration time.Duration
- ClientToUpstreamFrames int64
- UpstreamToClientFrames int64
- DroppedDownstreamFrames int64
- }
- type RelayTurnResult struct {
- RequestModel string
- ResponseModel string
- ResponseModelConflict bool
- ResponseServiceTier string
- Usage Usage
- RequestID string
- TerminalEventType string
- StartedAt time.Time
- Duration time.Duration
- FirstTokenMs *int
- }
- type RelayExit struct {
- Stage string
- Err error
- Graceful bool
- WroteDownstream bool
- }
- type RelayOptions struct {
- WriteTimeout time.Duration
- IdleTimeout time.Duration
- UpstreamDrainTimeout time.Duration
- FirstTurnStartedAt time.Time
- TakeNextTurnStartedAt func() time.Time
- FirstMessageType coderws.MessageType
- FirstMessageSent bool
- StartClientAfterFirstDownstream bool
- OnUsageParseFailure func(eventType string, usageRaw string)
- OnTurnComplete func(turn RelayTurnResult)
- BeforeWriteClient func(msgType coderws.MessageType, payload []byte, wroteDownstream bool) error
- BeforeClientWrite func(msgType coderws.MessageType, payload []byte)
- AfterClientWrite func(msgType coderws.MessageType, payload []byte, writeErr error)
- BeforeRelayCancel func(exit RelayExit)
- ReadClientFrame func(ctx context.Context, clientConn FrameConn) (coderws.MessageType, []byte, error)
- OnTrace func(event RelayTraceEvent)
- Now func() time.Time
- }
- type RelayTraceEvent struct {
- Stage string
- Direction string
- MessageType string
- PayloadBytes int
- Graceful bool
- WroteDownstream bool
- Error string
- }
- type relayState struct {
- usage Usage
- turnUsage Usage
- turnWroteDownstream atomic.Bool
- requestModelMu sync.RWMutex
- requestModel string
- pendingTurnStart atomic.Pointer[time.Time]
- lastResponseID string
- lastResponseModel string
- lastResponseServiceTier string
- responseConflict bool
- terminalEventType string
- firstTokenMs *int
- turnTimingByID map[string]*relayTurnTiming
- activeTurn *relayTurnTiming
- pendingBareError *observedUpstreamEvent
- }
- type relayExitSignal struct {
- stage string
- err error
- graceful bool
- wroteDownstream bool
- }
- type observedUpstreamEvent struct {
- terminal bool
- eventType string
- responseID string
- usage Usage
- startedAt time.Time
- responseModel string
- responseConflict bool
- responseServiceTier string
- duration time.Duration
- firstToken *int
- }
- type relayTurnTiming struct {
- startAt time.Time
- firstTokenMs *int
- firstResponseModel string
- terminalResponseModel string
- responseModelConflict bool
- // terminalResponseServiceTier is only taken from terminal events: earlier
- // events echo the requested tier, not the one the upstream actually used.
- terminalResponseServiceTier string
- }
- func Relay(
- ctx context.Context,
- clientConn FrameConn,
- upstreamConn FrameConn,
- firstClientMessage []byte,
- options RelayOptions,
- ) (RelayResult, *RelayExit) {
- result := RelayResult{RequestModel: strings.TrimSpace(gjson.GetBytes(firstClientMessage, "model").String())}
- if clientConn == nil || upstreamConn == nil {
- return result, &RelayExit{Stage: "relay_init", Err: errors.New("relay connection is nil")}
- }
- if ctx == nil {
- ctx = context.Background()
- }
- nowFn := options.Now
- if nowFn == nil {
- nowFn = time.Now
- }
- writeTimeout := options.WriteTimeout
- if writeTimeout <= 0 {
- writeTimeout = 2 * time.Minute
- }
- drainTimeout := options.UpstreamDrainTimeout
- if drainTimeout <= 0 {
- drainTimeout = 1200 * time.Millisecond
- }
- firstMessageType := options.FirstMessageType
- if firstMessageType != coderws.MessageBinary {
- firstMessageType = coderws.MessageText
- }
- startAt := nowFn()
- state := &relayState{requestModel: result.RequestModel}
- if isClientResponseCreateFrame(firstMessageType, firstClientMessage) {
- firstTurnStartedAt := options.FirstTurnStartedAt
- if firstTurnStartedAt.IsZero() {
- firstTurnStartedAt = startAt
- }
- state.setPendingTurnStartedAt(firstTurnStartedAt)
- }
- onTrace := options.OnTrace
- relayCtx, relayCancel := context.WithCancel(ctx)
- defer relayCancel()
- lastActivity := atomic.Int64{}
- lastActivity.Store(nowFn().UnixNano())
- markActivity := func() {
- lastActivity.Store(nowFn().UnixNano())
- }
- writeUpstream := func(msgType coderws.MessageType, payload []byte) error {
- writeCtx, cancel := context.WithTimeout(relayCtx, writeTimeout)
- defer cancel()
- return upstreamConn.WriteFrame(writeCtx, msgType, payload)
- }
- writeClientFrameUpstream := func(msgType coderws.MessageType, payload []byte) error {
- isResponseCreate := isClientResponseCreateFrame(msgType, payload)
- if isResponseCreate {
- state.setRequestModel(strings.TrimSpace(gjson.GetBytes(payload, "model").String()))
- turnStartedAt := time.Time{}
- if options.TakeNextTurnStartedAt != nil {
- turnStartedAt = options.TakeNextTurnStartedAt()
- }
- if turnStartedAt.IsZero() {
- turnStartedAt = nowFn()
- }
- state.setPendingTurnStartedAt(turnStartedAt)
- // The policy-enforcing client connection has accepted this turn.
- // Reset before the write so an immediate upstream response cannot race
- // with the transport returning from WriteFrame.
- state.turnWroteDownstream.Store(false)
- }
- err := writeUpstream(msgType, payload)
- if err != nil && isResponseCreate {
- // The relay exits on this error, but retain the previous turn's state
- // for accurate diagnostics while the two relay goroutines settle.
- state.turnWroteDownstream.Store(true)
- }
- return err
- }
- writeClient := func(msgType coderws.MessageType, payload []byte) error {
- // 下行写超时故意不挂在 relayCtx 上:coder/websocket 在已武装的 write
- // ctx 被取消时会直接硬关连接(context.AfterFunc 的 stop 不等待执行中
- // 的回调),外部取消若落在一次已成功写入的解除武装窗口内,会连同尚未
- // 发出的 close 帧一起冲掉,客户端只能看到裸 EOF 而收不到关闭码。与读
- // 侧 conn.Read(context.Background()) 同理,取消路径的连接回收由各退出
- // 分支的显式 Close/CloseNow 兜底。
- writeCtx, cancel := context.WithTimeout(context.Background(), writeTimeout)
- defer cancel()
- return clientConn.WriteFrame(writeCtx, msgType, payload)
- }
- clientToUpstreamFrames := &atomic.Int64{}
- upstreamToClientFrames := &atomic.Int64{}
- droppedDownstreamFrames := &atomic.Int64{}
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "relay_start",
- PayloadBytes: len(firstClientMessage),
- MessageType: relayMessageTypeString(firstMessageType),
- })
- if options.FirstMessageSent {
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "write_first_message_skipped",
- Direction: "client_to_upstream",
- MessageType: relayMessageTypeString(firstMessageType),
- PayloadBytes: len(firstClientMessage),
- })
- } else {
- if err := writeUpstream(firstMessageType, firstClientMessage); err != nil {
- result.Duration = nowFn().Sub(startAt)
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "write_first_message_failed",
- Direction: "client_to_upstream",
- MessageType: relayMessageTypeString(firstMessageType),
- PayloadBytes: len(firstClientMessage),
- Error: err.Error(),
- })
- return result, &RelayExit{Stage: "write_upstream", Err: err}
- }
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "write_first_message_ok",
- Direction: "client_to_upstream",
- MessageType: relayMessageTypeString(firstMessageType),
- PayloadBytes: len(firstClientMessage),
- })
- }
- clientToUpstreamFrames.Add(1)
- markActivity()
- exitCh := make(chan relayExitSignal, 3)
- dropDownstreamWrites := atomic.Bool{}
- clientReaderStarted := atomic.Bool{}
- startClientReader := func() {
- if !clientReaderStarted.CompareAndSwap(false, true) {
- return
- }
- go runClientToUpstream(relayCtx, clientConn, options.ReadClientFrame, writeClientFrameUpstream, markActivity, clientToUpstreamFrames, onTrace, exitCh)
- }
- if !options.StartClientAfterFirstDownstream {
- startClientReader()
- }
- upstreamDone := make(chan struct{})
- go func() {
- defer close(upstreamDone)
- runUpstreamToClient(
- relayCtx,
- upstreamConn,
- writeClient,
- startAt,
- nowFn,
- state,
- options.OnUsageParseFailure,
- options.OnTurnComplete,
- options.BeforeWriteClient,
- options.BeforeClientWrite,
- options.AfterClientWrite,
- func(msgType coderws.MessageType, payload []byte) {
- if options.StartClientAfterFirstDownstream {
- startClientReader()
- }
- },
- &dropDownstreamWrites,
- upstreamToClientFrames,
- droppedDownstreamFrames,
- markActivity,
- onTrace,
- exitCh,
- )
- }()
- go runIdleWatchdog(relayCtx, nowFn, options.IdleTimeout, &lastActivity, onTrace, exitCh)
- firstExit := <-exitCh
- // An outer ingress cancellation is a control-plane close, not a graceful
- // upstream disconnect. Leave the client connection open here so the
- // adapter can emit the precise lease/request close code. Internal
- // relayCancel does not cancel ctx and therefore does not take this path.
- if ctx.Err() != nil {
- firstExit.graceful = false
- }
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "first_exit",
- Direction: relayDirectionFromStage(firstExit.stage),
- Graceful: firstExit.graceful,
- WroteDownstream: firstExit.wroteDownstream,
- Error: relayErrorString(firstExit.err),
- })
- if options.BeforeRelayCancel != nil {
- options.BeforeRelayCancel(RelayExit{
- Stage: firstExit.stage,
- Err: firstExit.err,
- Graceful: firstExit.graceful,
- WroteDownstream: firstExit.wroteDownstream,
- })
- }
- combinedWroteDownstream := firstExit.wroteDownstream
- secondExit := relayExitSignal{graceful: true}
- hasSecondExit := false
- // 客户端断开后尽力继续读取上游短窗口,捕获延迟 usage/terminal 事件用于计费。
- if firstExit.stage == "read_client" && firstExit.graceful {
- dropDownstreamWrites.Store(true)
- secondExit, hasSecondExit = waitRelayExit(exitCh, drainTimeout)
- } else {
- relayCancel()
- _ = upstreamConn.Close()
- if clientReaderStarted.Load() {
- secondExit, hasSecondExit = waitRelayExit(exitCh, 200*time.Millisecond)
- }
- }
- if hasSecondExit {
- combinedWroteDownstream = combinedWroteDownstream || secondExit.wroteDownstream
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "second_exit",
- Direction: relayDirectionFromStage(secondExit.stage),
- Graceful: secondExit.graceful,
- WroteDownstream: secondExit.wroteDownstream,
- Error: relayErrorString(secondExit.err),
- })
- }
- relayCancel()
- _ = upstreamConn.Close()
- // ReadFrame observes relayCtx cancellation and Close is the transport-level
- // fallback. Join the reader before touching relayState or firing the final
- // turn callback; otherwise a late read can race Relay's result settlement.
- <-upstreamDone
- emitTurnComplete(options.OnTurnComplete, state, finalizePendingBareError(state, nowFn()))
- enrichResult(&result, state, nowFn().Sub(startAt))
- result.ClientToUpstreamFrames = clientToUpstreamFrames.Load()
- result.UpstreamToClientFrames = upstreamToClientFrames.Load()
- result.DroppedDownstreamFrames = droppedDownstreamFrames.Load()
- if options.FirstMessageSent && firstExit.stage == "read_client" && firstExit.graceful {
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "relay_client_closed",
- Graceful: true,
- WroteDownstream: combinedWroteDownstream,
- })
- return result, nil
- }
- if firstExit.stage == "read_client" && firstExit.graceful {
- stage := "client_disconnected"
- exitErr := firstExit.err
- if hasSecondExit && !secondExit.graceful {
- stage = secondExit.stage
- exitErr = secondExit.err
- }
- if exitErr == nil {
- exitErr = io.EOF
- }
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "relay_exit",
- Direction: relayDirectionFromStage(stage),
- Graceful: false,
- WroteDownstream: combinedWroteDownstream,
- Error: relayErrorString(exitErr),
- })
- return result, &RelayExit{
- Stage: stage,
- Err: exitErr,
- WroteDownstream: combinedWroteDownstream,
- }
- }
- if firstExit.graceful && (!hasSecondExit || secondExit.graceful) {
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "relay_complete",
- Graceful: true,
- WroteDownstream: combinedWroteDownstream,
- })
- _ = clientConn.Close()
- return result, nil
- }
- if !firstExit.graceful {
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "relay_exit",
- Direction: relayDirectionFromStage(firstExit.stage),
- Graceful: false,
- WroteDownstream: combinedWroteDownstream,
- Error: relayErrorString(firstExit.err),
- })
- return result, &RelayExit{
- Stage: firstExit.stage,
- Err: firstExit.err,
- WroteDownstream: combinedWroteDownstream,
- }
- }
- if hasSecondExit && !secondExit.graceful {
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "relay_exit",
- Direction: relayDirectionFromStage(secondExit.stage),
- Graceful: false,
- WroteDownstream: combinedWroteDownstream,
- Error: relayErrorString(secondExit.err),
- })
- return result, &RelayExit{
- Stage: secondExit.stage,
- Err: secondExit.err,
- WroteDownstream: combinedWroteDownstream,
- }
- }
- if options.FirstMessageSent {
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "relay_client_closed",
- Graceful: true,
- WroteDownstream: combinedWroteDownstream,
- })
- return result, nil
- }
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "relay_complete",
- Graceful: true,
- WroteDownstream: combinedWroteDownstream,
- })
- _ = clientConn.Close()
- return result, nil
- }
- func isClientResponseCreateFrame(msgType coderws.MessageType, payload []byte) bool {
- if msgType != coderws.MessageText && msgType != coderws.MessageBinary {
- return false
- }
- return strings.TrimSpace(gjson.GetBytes(payload, "type").String()) == "response.create"
- }
- func runClientToUpstream(
- ctx context.Context,
- clientConn FrameConn,
- readClientFrame func(context.Context, FrameConn) (coderws.MessageType, []byte, error),
- writeUpstream func(msgType coderws.MessageType, payload []byte) error,
- markActivity func(),
- forwardedFrames *atomic.Int64,
- onTrace func(event RelayTraceEvent),
- exitCh chan<- relayExitSignal,
- ) {
- if readClientFrame == nil {
- readClientFrame = func(ctx context.Context, conn FrameConn) (coderws.MessageType, []byte, error) {
- return conn.ReadFrame(ctx)
- }
- }
- for {
- msgType, payload, err := readClientFrame(ctx, clientConn)
- if err != nil {
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "read_client_failed",
- Direction: "client_to_upstream",
- Error: err.Error(),
- Graceful: isDisconnectError(err),
- })
- exitCh <- relayExitSignal{stage: "read_client", err: err, graceful: isDisconnectError(err)}
- return
- }
- markActivity()
- if err := writeUpstream(msgType, payload); err != nil {
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "write_upstream_failed",
- Direction: "client_to_upstream",
- MessageType: relayMessageTypeString(msgType),
- PayloadBytes: len(payload),
- Error: err.Error(),
- })
- exitCh <- relayExitSignal{stage: "write_upstream", err: err}
- return
- }
- if forwardedFrames != nil {
- forwardedFrames.Add(1)
- }
- markActivity()
- }
- }
- func runUpstreamToClient(
- ctx context.Context,
- upstreamConn FrameConn,
- writeClient func(msgType coderws.MessageType, payload []byte) error,
- startAt time.Time,
- nowFn func() time.Time,
- state *relayState,
- onUsageParseFailure func(eventType string, usageRaw string),
- onTurnComplete func(turn RelayTurnResult),
- beforeWriteClient func(msgType coderws.MessageType, payload []byte, wroteDownstream bool) error,
- beforeClientWrite func(msgType coderws.MessageType, payload []byte),
- afterClientWrite func(msgType coderws.MessageType, payload []byte, writeErr error),
- afterWriteClient func(msgType coderws.MessageType, payload []byte),
- dropDownstreamWrites *atomic.Bool,
- forwardedFrames *atomic.Int64,
- droppedFrames *atomic.Int64,
- markActivity func(),
- onTrace func(event RelayTraceEvent),
- exitCh chan<- relayExitSignal,
- ) {
- wroteDownstream := false
- for {
- msgType, payload, err := upstreamConn.ReadFrame(ctx)
- if err != nil {
- emitTurnComplete(onTurnComplete, state, finalizePendingBareError(state, nowFn()))
- graceful := isDisconnectError(err)
- // A clean WebSocket close only describes the transport handshake. Once
- // the upstream has started a Responses turn, success still requires a
- // terminal protocol event. Treat an early 1000/EOF as a relay failure so
- // the adapter does not report relay_completed with an active turn.
- if graceful && state.hasUnfinishedTurn() {
- graceful = false
- err = errors.New("upstream websocket closed before terminal event: " + err.Error())
- }
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "read_upstream_failed",
- Direction: "upstream_to_client",
- Error: err.Error(),
- Graceful: graceful,
- WroteDownstream: wroteDownstream,
- })
- exitCh <- relayExitSignal{
- stage: "read_upstream",
- err: err,
- graceful: graceful,
- wroteDownstream: wroteDownstream,
- }
- return
- }
- markActivity()
- if beforeWriteClient != nil {
- wroteDownstreamInTurn := wroteDownstream
- if state != nil {
- wroteDownstreamInTurn = state.turnWroteDownstream.Load()
- }
- if err := beforeWriteClient(msgType, payload, wroteDownstreamInTurn); err != nil {
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "upstream_message_rejected",
- Direction: "upstream_to_client",
- MessageType: relayMessageTypeString(msgType),
- PayloadBytes: len(payload),
- WroteDownstream: wroteDownstream,
- Error: err.Error(),
- })
- exitCh <- relayExitSignal{
- stage: "upstream_message",
- err: err,
- wroteDownstream: wroteDownstream,
- }
- return
- }
- }
- observedEvent := observedUpstreamEvent{}
- switch msgType {
- case coderws.MessageText:
- eventType := strings.TrimSpace(gjson.GetBytes(payload, "type").String())
- if shouldFinalizePendingBareError(state, payload, eventType) {
- emitTurnComplete(onTurnComplete, state, finalizePendingBareError(state, nowFn()))
- }
- observedEvent = observeUpstreamMessage(state, payload, startAt, nowFn, onUsageParseFailure)
- case coderws.MessageBinary:
- // Binary frames remain opaque for usage/result observation, but a JSON
- // terminal still settles relay lifecycle. Otherwise the pending-turn
- // disconnect guard would turn an already-delivered terminal into a false
- // missing-terminal failure when the upstream closes normally.
- if isTerminalEvent(strings.TrimSpace(gjson.GetBytes(payload, "type").String())) {
- state.consumePendingTurnStartedAt()
- openAIWSRelayDiscardActiveTurnTiming(state)
- }
- }
- emitTurnComplete(onTurnComplete, state, observedEvent)
- if dropDownstreamWrites != nil && dropDownstreamWrites.Load() {
- if droppedFrames != nil {
- droppedFrames.Add(1)
- }
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "drop_downstream_frame",
- Direction: "upstream_to_client",
- MessageType: relayMessageTypeString(msgType),
- PayloadBytes: len(payload),
- WroteDownstream: wroteDownstream,
- })
- if observedEvent.terminal {
- exitCh <- relayExitSignal{
- stage: "drain_terminal",
- graceful: true,
- wroteDownstream: wroteDownstream,
- }
- return
- }
- markActivity()
- continue
- }
- if beforeClientWrite != nil {
- beforeClientWrite(msgType, payload)
- }
- writeErr := writeClient(msgType, payload)
- if afterClientWrite != nil {
- afterClientWrite(msgType, payload, writeErr)
- }
- if writeErr != nil {
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "write_client_failed",
- Direction: "upstream_to_client",
- MessageType: relayMessageTypeString(msgType),
- PayloadBytes: len(payload),
- WroteDownstream: wroteDownstream,
- Error: writeErr.Error(),
- })
- exitCh <- relayExitSignal{stage: "write_client", err: writeErr, wroteDownstream: wroteDownstream}
- return
- }
- wroteDownstream = true
- if state != nil {
- state.turnWroteDownstream.Store(true)
- }
- if afterWriteClient != nil {
- afterWriteClient(msgType, payload)
- }
- if forwardedFrames != nil {
- forwardedFrames.Add(1)
- }
- markActivity()
- }
- }
- func runIdleWatchdog(
- ctx context.Context,
- nowFn func() time.Time,
- idleTimeout time.Duration,
- lastActivity *atomic.Int64,
- onTrace func(event RelayTraceEvent),
- exitCh chan<- relayExitSignal,
- ) {
- if idleTimeout <= 0 {
- return
- }
- checkInterval := minDuration(idleTimeout/4, 5*time.Second)
- if checkInterval < time.Second {
- checkInterval = time.Second
- }
- ticker := time.NewTicker(checkInterval)
- defer ticker.Stop()
- for {
- select {
- case <-ctx.Done():
- return
- case <-ticker.C:
- last := time.Unix(0, lastActivity.Load())
- if nowFn().Sub(last) < idleTimeout {
- continue
- }
- emitRelayTrace(onTrace, RelayTraceEvent{
- Stage: "idle_timeout_triggered",
- Direction: "watchdog",
- Error: context.DeadlineExceeded.Error(),
- })
- exitCh <- relayExitSignal{stage: "idle_timeout", err: context.DeadlineExceeded}
- return
- }
- }
- }
- func emitRelayTrace(onTrace func(event RelayTraceEvent), event RelayTraceEvent) {
- if onTrace == nil {
- return
- }
- onTrace(event)
- }
- func relayMessageTypeString(msgType coderws.MessageType) string {
- switch msgType {
- case coderws.MessageText:
- return "text"
- case coderws.MessageBinary:
- return "binary"
- default:
- return "unknown(" + strconv.Itoa(int(msgType)) + ")"
- }
- }
- func relayDirectionFromStage(stage string) string {
- switch stage {
- case "read_client", "write_upstream":
- return "client_to_upstream"
- case "read_upstream", "write_client", "drain_terminal":
- return "upstream_to_client"
- case "idle_timeout":
- return "watchdog"
- default:
- return ""
- }
- }
- func relayErrorString(err error) string {
- if err == nil {
- return ""
- }
- return err.Error()
- }
- func observeUpstreamMessage(
- state *relayState,
- message []byte,
- startAt time.Time,
- nowFn func() time.Time,
- onUsageParseFailure func(eventType string, usageRaw string),
- ) observedUpstreamEvent {
- if state == nil || len(message) == 0 {
- return observedUpstreamEvent{}
- }
- values := gjson.GetManyBytes(message, "type", "response.id", "response_id", "id")
- eventType := strings.TrimSpace(values[0].String())
- if eventType == "" {
- return observedUpstreamEvent{}
- }
- responseID := strings.TrimSpace(values[1].String())
- if responseID == "" {
- responseID = strings.TrimSpace(values[2].String())
- }
- // 仅 terminal 事件兜底读取顶层 id,避免把 event_id 当成 response_id 关联到 turn。
- if responseID == "" && isTerminalEvent(eventType) {
- responseID = strings.TrimSpace(values[3].String())
- }
- now := nowFn()
- if state.firstTokenMs == nil && isTokenEvent(eventType) {
- ms := int(now.Sub(startAt).Milliseconds())
- if ms >= 0 {
- state.firstTokenMs = &ms
- }
- if state.activeTurn != nil && state.activeTurn.firstTokenMs == nil {
- tms := int(now.Sub(state.activeTurn.startAt).Milliseconds())
- if tms >= 0 {
- state.activeTurn.firstTokenMs = &tms
- }
- }
- }
- parsedUsage := parseUsageAndAccumulate(state, message, eventType, onUsageParseFailure)
- observed := observedUpstreamEvent{
- eventType: eventType,
- responseID: responseID,
- usage: parsedUsage,
- }
- var turnTiming *relayTurnTiming
- if responseID != "" {
- turnTiming = openAIWSRelayGetOrInitTurnTiming(state, responseID, now)
- if turnTiming != nil && turnTiming.firstTokenMs == nil && isTokenEvent(eventType) {
- ms := int(now.Sub(turnTiming.startAt).Milliseconds())
- if ms >= 0 {
- turnTiming.firstTokenMs = &ms
- }
- }
- } else {
- turnTiming = state.activeTurn
- }
- observeRelayTurnResponseModel(turnTiming, firstRelayResponseModel(message), isTerminalEvent(eventType))
- if !isTerminalEvent(eventType) {
- return observed
- }
- observeRelayTurnResponseServiceTier(turnTiming, firstRelayResponseServiceTier(message))
- state.terminalEventType = eventType
- if eventType == "error" {
- // Some Responses servers emit error immediately before response.failed.
- // Defer turn settlement so the authoritative failed usage can replace
- // this fallback instead of billing both terminal frames.
- if observed.responseID == "" {
- observed.responseID = openAIWSRelayActiveTurnID(state)
- }
- pending := observed
- state.pendingBareError = &pending
- return observed
- }
- state.pendingBareError = nil
- return finalizeObservedRelayTerminal(state, observed, now)
- }
- func shouldFinalizePendingBareError(state *relayState, payload []byte, eventType string) bool {
- if state == nil || state.pendingBareError == nil {
- return false
- }
- eventType = strings.TrimSpace(eventType)
- if eventType == "" || eventType == "error" || eventType == "response.failed" {
- return false
- }
- if isTerminalEvent(eventType) || eventType == "response.created" {
- return true
- }
- // Auxiliary provider frames may be interleaved between error and its
- // authoritative response.failed. Only a response event identifying a
- // different turn closes the pending error.
- responseID := strings.TrimSpace(gjson.GetBytes(payload, "response.id").String())
- if responseID == "" || state.pendingBareError.responseID == "" {
- return false
- }
- return responseID != state.pendingBareError.responseID
- }
- func finalizePendingBareError(state *relayState, now time.Time) observedUpstreamEvent {
- if state == nil || state.pendingBareError == nil {
- return observedUpstreamEvent{}
- }
- observed := *state.pendingBareError
- state.pendingBareError = nil
- return finalizeObservedRelayTerminal(state, observed, now)
- }
- func finalizeObservedRelayTerminal(state *relayState, observed observedUpstreamEvent, now time.Time) observedUpstreamEvent {
- if state == nil || strings.TrimSpace(observed.eventType) == "" {
- return observedUpstreamEvent{}
- }
- observed.usage = finalizeRelayTurnUsage(state)
- observed.terminal = true
- responseID := strings.TrimSpace(observed.responseID)
- if responseID != "" {
- state.lastResponseID = responseID
- if turnTiming, ok := openAIWSRelayDeleteTurnTiming(state, responseID); ok {
- observed.responseModel = relayTurnResponseModel(&turnTiming)
- observed.responseConflict = turnTiming.responseModelConflict
- observed.responseServiceTier = turnTiming.terminalResponseServiceTier
- state.lastResponseModel = observed.responseModel
- state.responseConflict = observed.responseConflict
- state.lastResponseServiceTier = observed.responseServiceTier
- duration := now.Sub(turnTiming.startAt)
- if duration < 0 {
- duration = 0
- }
- observed.startedAt = turnTiming.startAt
- observed.duration = duration
- observed.firstToken = openAIWSRelayCloneIntPtr(turnTiming.firstTokenMs)
- }
- } else {
- state.consumePendingTurnStartedAt()
- openAIWSRelayDiscardActiveTurnTiming(state)
- }
- return observed
- }
- func emitTurnComplete(
- onTurnComplete func(turn RelayTurnResult),
- state *relayState,
- observed observedUpstreamEvent,
- ) {
- if onTurnComplete == nil || !observed.terminal {
- return
- }
- responseID := strings.TrimSpace(observed.responseID)
- if responseID == "" && strings.TrimSpace(observed.eventType) != "error" {
- return
- }
- requestModel := ""
- if state != nil {
- requestModel = state.currentRequestModel()
- }
- onTurnComplete(RelayTurnResult{
- RequestModel: requestModel,
- ResponseModel: observed.responseModel,
- ResponseModelConflict: observed.responseConflict,
- ResponseServiceTier: observed.responseServiceTier,
- Usage: observed.usage,
- RequestID: responseID,
- TerminalEventType: observed.eventType,
- StartedAt: observed.startedAt,
- Duration: observed.duration,
- FirstTokenMs: openAIWSRelayCloneIntPtr(observed.firstToken),
- })
- }
- func firstRelayResponseModel(message []byte) string {
- if len(message) == 0 {
- return ""
- }
- values := gjson.GetManyBytes(message, "response.model", "model")
- for _, value := range values {
- if value.Type != gjson.String {
- continue
- }
- if model := strings.TrimSpace(value.String()); model != "" {
- return model
- }
- }
- return ""
- }
- func observeRelayTurnResponseModel(turn *relayTurnTiming, model string, terminal bool) {
- if turn == nil {
- return
- }
- model = strings.TrimSpace(model)
- if model == "" {
- return
- }
- current := relayTurnResponseModel(turn)
- if current != "" && !strings.EqualFold(current, model) {
- turn.responseModelConflict = true
- }
- if terminal {
- turn.terminalResponseModel = model
- return
- }
- if turn.firstResponseModel == "" {
- turn.firstResponseModel = model
- }
- }
- func relayTurnResponseModel(turn *relayTurnTiming) string {
- if turn == nil {
- return ""
- }
- if turn.terminalResponseModel != "" {
- return turn.terminalResponseModel
- }
- return turn.firstResponseModel
- }
- func firstRelayResponseServiceTier(message []byte) string {
- if len(message) == 0 {
- return ""
- }
- values := gjson.GetManyBytes(message, "response.service_tier", "service_tier")
- for _, value := range values {
- if value.Type != gjson.String {
- continue
- }
- if tier := strings.TrimSpace(value.String()); tier != "" {
- return tier
- }
- }
- return ""
- }
- func observeRelayTurnResponseServiceTier(turn *relayTurnTiming, tier string) {
- if turn == nil {
- return
- }
- if tier = strings.TrimSpace(tier); tier != "" {
- turn.terminalResponseServiceTier = tier
- }
- }
- func openAIWSRelayGetOrInitTurnTiming(state *relayState, responseID string, now time.Time) *relayTurnTiming {
- if state == nil {
- return nil
- }
- if state.turnTimingByID == nil {
- state.turnTimingByID = make(map[string]*relayTurnTiming, 8)
- }
- timing, ok := state.turnTimingByID[responseID]
- if !ok || timing == nil || timing.startAt.IsZero() {
- startAt := state.consumePendingTurnStartedAt()
- if startAt.IsZero() {
- startAt = now
- }
- timing = &relayTurnTiming{startAt: startAt}
- state.turnTimingByID[responseID] = timing
- state.activeTurn = timing
- return timing
- }
- return timing
- }
- func (s *relayState) setPendingTurnStartedAt(startedAt time.Time) {
- if s == nil || startedAt.IsZero() {
- return
- }
- startedAtCopy := startedAt
- s.pendingTurnStart.Store(&startedAtCopy)
- }
- func (s *relayState) consumePendingTurnStartedAt() time.Time {
- if s == nil {
- return time.Time{}
- }
- startedAt := s.pendingTurnStart.Swap(nil)
- if startedAt == nil {
- return time.Time{}
- }
- return *startedAt
- }
- func (s *relayState) hasUnfinishedTurn() bool {
- if s == nil {
- return false
- }
- return s.pendingTurnStart.Load() != nil || s.activeTurn != nil
- }
- func openAIWSRelayDeleteTurnTiming(state *relayState, responseID string) (relayTurnTiming, bool) {
- if state == nil || state.turnTimingByID == nil {
- return relayTurnTiming{}, false
- }
- timing, ok := state.turnTimingByID[responseID]
- if !ok || timing == nil {
- return relayTurnTiming{}, false
- }
- delete(state.turnTimingByID, responseID)
- if state.activeTurn == timing {
- state.activeTurn = nil
- }
- return *timing, true
- }
- func openAIWSRelayDiscardActiveTurnTiming(state *relayState) {
- if state == nil || state.activeTurn == nil {
- return
- }
- active := state.activeTurn
- for responseID, timing := range state.turnTimingByID {
- if timing == active {
- delete(state.turnTimingByID, responseID)
- }
- }
- state.activeTurn = nil
- }
- func openAIWSRelayActiveTurnID(state *relayState) string {
- if state == nil || state.activeTurn == nil {
- return ""
- }
- for responseID, timing := range state.turnTimingByID {
- if timing == state.activeTurn {
- return responseID
- }
- }
- return ""
- }
- func openAIWSRelayCloneIntPtr(v *int) *int {
- if v == nil {
- return nil
- }
- cloned := *v
- return &cloned
- }
- func parseUsageAndAccumulate(
- state *relayState,
- message []byte,
- eventType string,
- onParseFailure func(eventType string, usageRaw string),
- ) Usage {
- if state == nil || len(message) == 0 || !shouldParseUsage(eventType) || !bytes.Contains(message, []byte(`"usage"`)) {
- return Usage{}
- }
- usageResult := gjson.GetBytes(message, "response.usage")
- if !usageResult.Exists() {
- usageResult = gjson.GetBytes(message, "usage")
- }
- if !usageResult.Exists() {
- return Usage{}
- }
- usageRaw := strings.TrimSpace(usageResult.Raw)
- if usageRaw == "" || !strings.HasPrefix(usageRaw, "{") {
- recordUsageParseFailure()
- if onParseFailure != nil {
- onParseFailure(eventType, usageRaw)
- }
- return Usage{}
- }
- inputResult := usageResult.Get("input_tokens")
- if !inputResult.Exists() {
- inputResult = usageResult.Get("prompt_tokens")
- }
- outputResult := usageResult.Get("output_tokens")
- if !outputResult.Exists() {
- outputResult = usageResult.Get("completion_tokens")
- }
- cachedResult := usageResult.Get("input_tokens_details.cached_tokens")
- if !cachedResult.Exists() {
- cachedResult = usageResult.Get("prompt_tokens_details.cached_tokens")
- }
- imageTokens := usageResult.Get("output_tokens_details.image_tokens").Int()
- if imageTokens == 0 {
- imageTokens = usageResult.Get("completion_tokens_details.image_tokens").Int()
- }
- requireTotals := isTerminalEvent(strings.TrimSpace(eventType))
- inputTokens, inputOK := parseUsageIntField(inputResult, requireTotals)
- outputTokens, outputOK := parseUsageIntField(outputResult, requireTotals)
- cachedTokens, cachedOK := parseUsageIntField(cachedResult, false)
- if !inputOK || !outputOK || !cachedOK {
- recordUsageParseFailure()
- if onParseFailure != nil {
- onParseFailure(eventType, usageRaw)
- }
- // 解析失败时不做部分字段累加,避免计费 usage 出现“半有效”状态。
- return Usage{}
- }
- reasoningTokens := usageResult.Get("output_tokens_details.reasoning_tokens").Int()
- if reasoningTokens == 0 {
- reasoningTokens = usageResult.Get("completion_tokens_details.reasoning_tokens").Int()
- }
- if reasoningTokens > 0 {
- outputTokens = int(xai.IncludeIndependentReasoningTokens(
- int64(inputTokens), int64(outputTokens), usageResult.Get("total_tokens").Int(), reasoningTokens,
- ))
- }
- parsedUsage := Usage{
- InputTokens: inputTokens,
- OutputTokens: outputTokens,
- CacheCreationInputTokens: openAICacheCreationTokensFromUsage(usageResult),
- CacheReadInputTokens: cachedTokens,
- ImageOutputTokens: int(imageTokens),
- }
- if isTerminalEvent(strings.TrimSpace(eventType)) {
- if relayUsageHasTokens(parsedUsage) || !relayUsageHasTokens(state.turnUsage) {
- state.turnUsage = parsedUsage
- }
- } else {
- mergeRelayUsageNonZero(&state.turnUsage, parsedUsage)
- return Usage{}
- }
- return parsedUsage
- }
- func relayUsageHasTokens(usage Usage) bool {
- return usage.InputTokens > 0 || usage.OutputTokens > 0 ||
- usage.CacheCreationInputTokens > 0 || usage.CacheReadInputTokens > 0 ||
- usage.ImageOutputTokens > 0
- }
- func mergeRelayUsageNonZero(dst *Usage, src Usage) {
- if dst == nil {
- return
- }
- if src.InputTokens > 0 {
- dst.InputTokens = src.InputTokens
- }
- if src.OutputTokens > 0 {
- dst.OutputTokens = src.OutputTokens
- }
- if src.CacheCreationInputTokens > 0 {
- dst.CacheCreationInputTokens = src.CacheCreationInputTokens
- }
- if src.CacheReadInputTokens > 0 {
- dst.CacheReadInputTokens = src.CacheReadInputTokens
- }
- if src.ImageOutputTokens > 0 {
- dst.ImageOutputTokens = src.ImageOutputTokens
- }
- }
- func finalizeRelayTurnUsage(state *relayState) Usage {
- if state == nil {
- return Usage{}
- }
- turnUsage := state.turnUsage
- state.usage.InputTokens += turnUsage.InputTokens
- state.usage.OutputTokens += turnUsage.OutputTokens
- state.usage.CacheCreationInputTokens += turnUsage.CacheCreationInputTokens
- state.usage.CacheReadInputTokens += turnUsage.CacheReadInputTokens
- state.usage.ImageOutputTokens += turnUsage.ImageOutputTokens
- state.turnUsage = Usage{}
- return turnUsage
- }
- func parseUsageIntField(value gjson.Result, required bool) (int, bool) {
- if !value.Exists() {
- return 0, !required
- }
- if value.Type != gjson.Number {
- return 0, false
- }
- return int(value.Int()), true
- }
- func openAICacheCreationTokensFromUsage(value gjson.Result) int {
- for _, field := range []string{
- "input_tokens_details.cache_write_tokens",
- "prompt_tokens_details.cache_write_tokens",
- "input_tokens_details.cache_creation_tokens",
- "prompt_tokens_details.cache_creation_tokens",
- } {
- result := value.Get(field)
- if result.Exists() {
- return max(int(result.Int()), 0)
- }
- }
- for _, field := range []string{
- "cache_write_tokens",
- "cache_creation_input_tokens",
- "cache_write_input_tokens",
- "cache_creation_tokens",
- } {
- if tokens := int(value.Get(field).Int()); tokens > 0 {
- return tokens
- }
- }
- return 0
- }
- func enrichResult(result *RelayResult, state *relayState, duration time.Duration) {
- if result == nil {
- return
- }
- result.Duration = duration
- if state == nil {
- return
- }
- result.RequestModel = state.currentRequestModel()
- result.ResponseModel = state.lastResponseModel
- result.ResponseModelConflict = state.responseConflict
- result.ResponseServiceTier = state.lastResponseServiceTier
- result.Usage = state.usage
- result.RequestID = state.lastResponseID
- result.TerminalEventType = state.terminalEventType
- result.FirstTokenMs = state.firstTokenMs
- }
- func (s *relayState) setRequestModel(model string) {
- if s == nil || model == "" {
- return
- }
- s.requestModelMu.Lock()
- s.requestModel = model
- s.requestModelMu.Unlock()
- }
- func (s *relayState) currentRequestModel() string {
- if s == nil {
- return ""
- }
- s.requestModelMu.RLock()
- defer s.requestModelMu.RUnlock()
- return s.requestModel
- }
- func isDisconnectError(err error) bool {
- if err == nil {
- return false
- }
- if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) || errors.Is(err, context.Canceled) {
- return true
- }
- switch coderws.CloseStatus(err) {
- case coderws.StatusNormalClosure, coderws.StatusGoingAway, coderws.StatusNoStatusRcvd, coderws.StatusAbnormalClosure:
- return true
- }
- message := strings.ToLower(strings.TrimSpace(err.Error()))
- if message == "" {
- return false
- }
- return strings.Contains(message, "failed to read frame header: eof") ||
- strings.Contains(message, "unexpected eof") ||
- strings.Contains(message, "use of closed network connection") ||
- strings.Contains(message, "connection reset by peer") ||
- strings.Contains(message, "broken pipe")
- }
- func isTerminalEvent(eventType string) bool {
- switch strings.TrimSpace(eventType) {
- case "response.completed", "response.done", "response.failed", "response.incomplete", "response.cancelled", "response.canceled", "error":
- return true
- default:
- return false
- }
- }
- func shouldParseUsage(eventType string) bool {
- eventType = strings.TrimSpace(eventType)
- if eventType == "error" || isTerminalEvent(eventType) {
- return true
- }
- return strings.HasPrefix(eventType, "response.") && !strings.HasSuffix(eventType, ".delta")
- }
- func isTokenEvent(eventType string) bool {
- eventType = strings.TrimSpace(eventType)
- return strings.HasSuffix(eventType, ".delta") ||
- eventType == "response.output_text.done" ||
- eventType == "response.function_call_arguments.done"
- }
- func minDuration(a, b time.Duration) time.Duration {
- if a <= 0 {
- return b
- }
- if b <= 0 {
- return a
- }
- if a < b {
- return a
- }
- return b
- }
- func waitRelayExit(exitCh <-chan relayExitSignal, timeout time.Duration) (relayExitSignal, bool) {
- if timeout <= 0 {
- timeout = 200 * time.Millisecond
- }
- select {
- case sig := <-exitCh:
- return sig, true
- case <-time.After(timeout):
- return relayExitSignal{}, false
- }
- }
|