passthrough_relay.go 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342
  1. package openai_ws_v2
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "io"
  7. "net"
  8. "strconv"
  9. "strings"
  10. "sync"
  11. "sync/atomic"
  12. "time"
  13. coderws "github.com/coder/websocket"
  14. "github.com/tidwall/gjson"
  15. "github.com/Wei-Shaw/sub2api/internal/pkg/xai"
  16. )
  17. type FrameConn interface {
  18. ReadFrame(ctx context.Context) (coderws.MessageType, []byte, error)
  19. WriteFrame(ctx context.Context, msgType coderws.MessageType, payload []byte) error
  20. Close() error
  21. }
  22. type Usage struct {
  23. InputTokens int
  24. OutputTokens int
  25. CacheCreationInputTokens int
  26. CacheReadInputTokens int
  27. ImageOutputTokens int
  28. }
  29. type RelayResult struct {
  30. RequestModel string
  31. ResponseModel string
  32. ResponseModelConflict bool
  33. // ResponseServiceTier is the raw service_tier declared by the last terminal
  34. // response event; "" when the upstream never declared one.
  35. ResponseServiceTier string
  36. Usage Usage
  37. RequestID string
  38. TerminalEventType string
  39. FirstTokenMs *int
  40. Duration time.Duration
  41. ClientToUpstreamFrames int64
  42. UpstreamToClientFrames int64
  43. DroppedDownstreamFrames int64
  44. }
  45. type RelayTurnResult struct {
  46. RequestModel string
  47. ResponseModel string
  48. ResponseModelConflict bool
  49. ResponseServiceTier string
  50. Usage Usage
  51. RequestID string
  52. TerminalEventType string
  53. StartedAt time.Time
  54. Duration time.Duration
  55. FirstTokenMs *int
  56. }
  57. type RelayExit struct {
  58. Stage string
  59. Err error
  60. Graceful bool
  61. WroteDownstream bool
  62. }
  63. type RelayOptions struct {
  64. WriteTimeout time.Duration
  65. IdleTimeout time.Duration
  66. UpstreamDrainTimeout time.Duration
  67. FirstTurnStartedAt time.Time
  68. TakeNextTurnStartedAt func() time.Time
  69. FirstMessageType coderws.MessageType
  70. FirstMessageSent bool
  71. StartClientAfterFirstDownstream bool
  72. OnUsageParseFailure func(eventType string, usageRaw string)
  73. OnTurnComplete func(turn RelayTurnResult)
  74. BeforeWriteClient func(msgType coderws.MessageType, payload []byte, wroteDownstream bool) error
  75. BeforeClientWrite func(msgType coderws.MessageType, payload []byte)
  76. AfterClientWrite func(msgType coderws.MessageType, payload []byte, writeErr error)
  77. BeforeRelayCancel func(exit RelayExit)
  78. ReadClientFrame func(ctx context.Context, clientConn FrameConn) (coderws.MessageType, []byte, error)
  79. OnTrace func(event RelayTraceEvent)
  80. Now func() time.Time
  81. }
  82. type RelayTraceEvent struct {
  83. Stage string
  84. Direction string
  85. MessageType string
  86. PayloadBytes int
  87. Graceful bool
  88. WroteDownstream bool
  89. Error string
  90. }
  91. type relayState struct {
  92. usage Usage
  93. turnUsage Usage
  94. turnWroteDownstream atomic.Bool
  95. requestModelMu sync.RWMutex
  96. requestModel string
  97. pendingTurnStart atomic.Pointer[time.Time]
  98. lastResponseID string
  99. lastResponseModel string
  100. lastResponseServiceTier string
  101. responseConflict bool
  102. terminalEventType string
  103. firstTokenMs *int
  104. turnTimingByID map[string]*relayTurnTiming
  105. activeTurn *relayTurnTiming
  106. pendingBareError *observedUpstreamEvent
  107. }
  108. type relayExitSignal struct {
  109. stage string
  110. err error
  111. graceful bool
  112. wroteDownstream bool
  113. }
  114. type observedUpstreamEvent struct {
  115. terminal bool
  116. eventType string
  117. responseID string
  118. usage Usage
  119. startedAt time.Time
  120. responseModel string
  121. responseConflict bool
  122. responseServiceTier string
  123. duration time.Duration
  124. firstToken *int
  125. }
  126. type relayTurnTiming struct {
  127. startAt time.Time
  128. firstTokenMs *int
  129. firstResponseModel string
  130. terminalResponseModel string
  131. responseModelConflict bool
  132. // terminalResponseServiceTier is only taken from terminal events: earlier
  133. // events echo the requested tier, not the one the upstream actually used.
  134. terminalResponseServiceTier string
  135. }
  136. func Relay(
  137. ctx context.Context,
  138. clientConn FrameConn,
  139. upstreamConn FrameConn,
  140. firstClientMessage []byte,
  141. options RelayOptions,
  142. ) (RelayResult, *RelayExit) {
  143. result := RelayResult{RequestModel: strings.TrimSpace(gjson.GetBytes(firstClientMessage, "model").String())}
  144. if clientConn == nil || upstreamConn == nil {
  145. return result, &RelayExit{Stage: "relay_init", Err: errors.New("relay connection is nil")}
  146. }
  147. if ctx == nil {
  148. ctx = context.Background()
  149. }
  150. nowFn := options.Now
  151. if nowFn == nil {
  152. nowFn = time.Now
  153. }
  154. writeTimeout := options.WriteTimeout
  155. if writeTimeout <= 0 {
  156. writeTimeout = 2 * time.Minute
  157. }
  158. drainTimeout := options.UpstreamDrainTimeout
  159. if drainTimeout <= 0 {
  160. drainTimeout = 1200 * time.Millisecond
  161. }
  162. firstMessageType := options.FirstMessageType
  163. if firstMessageType != coderws.MessageBinary {
  164. firstMessageType = coderws.MessageText
  165. }
  166. startAt := nowFn()
  167. state := &relayState{requestModel: result.RequestModel}
  168. if isClientResponseCreateFrame(firstMessageType, firstClientMessage) {
  169. firstTurnStartedAt := options.FirstTurnStartedAt
  170. if firstTurnStartedAt.IsZero() {
  171. firstTurnStartedAt = startAt
  172. }
  173. state.setPendingTurnStartedAt(firstTurnStartedAt)
  174. }
  175. onTrace := options.OnTrace
  176. relayCtx, relayCancel := context.WithCancel(ctx)
  177. defer relayCancel()
  178. lastActivity := atomic.Int64{}
  179. lastActivity.Store(nowFn().UnixNano())
  180. markActivity := func() {
  181. lastActivity.Store(nowFn().UnixNano())
  182. }
  183. writeUpstream := func(msgType coderws.MessageType, payload []byte) error {
  184. writeCtx, cancel := context.WithTimeout(relayCtx, writeTimeout)
  185. defer cancel()
  186. return upstreamConn.WriteFrame(writeCtx, msgType, payload)
  187. }
  188. writeClientFrameUpstream := func(msgType coderws.MessageType, payload []byte) error {
  189. isResponseCreate := isClientResponseCreateFrame(msgType, payload)
  190. if isResponseCreate {
  191. state.setRequestModel(strings.TrimSpace(gjson.GetBytes(payload, "model").String()))
  192. turnStartedAt := time.Time{}
  193. if options.TakeNextTurnStartedAt != nil {
  194. turnStartedAt = options.TakeNextTurnStartedAt()
  195. }
  196. if turnStartedAt.IsZero() {
  197. turnStartedAt = nowFn()
  198. }
  199. state.setPendingTurnStartedAt(turnStartedAt)
  200. // The policy-enforcing client connection has accepted this turn.
  201. // Reset before the write so an immediate upstream response cannot race
  202. // with the transport returning from WriteFrame.
  203. state.turnWroteDownstream.Store(false)
  204. }
  205. err := writeUpstream(msgType, payload)
  206. if err != nil && isResponseCreate {
  207. // The relay exits on this error, but retain the previous turn's state
  208. // for accurate diagnostics while the two relay goroutines settle.
  209. state.turnWroteDownstream.Store(true)
  210. }
  211. return err
  212. }
  213. writeClient := func(msgType coderws.MessageType, payload []byte) error {
  214. // 下行写超时故意不挂在 relayCtx 上:coder/websocket 在已武装的 write
  215. // ctx 被取消时会直接硬关连接(context.AfterFunc 的 stop 不等待执行中
  216. // 的回调),外部取消若落在一次已成功写入的解除武装窗口内,会连同尚未
  217. // 发出的 close 帧一起冲掉,客户端只能看到裸 EOF 而收不到关闭码。与读
  218. // 侧 conn.Read(context.Background()) 同理,取消路径的连接回收由各退出
  219. // 分支的显式 Close/CloseNow 兜底。
  220. writeCtx, cancel := context.WithTimeout(context.Background(), writeTimeout)
  221. defer cancel()
  222. return clientConn.WriteFrame(writeCtx, msgType, payload)
  223. }
  224. clientToUpstreamFrames := &atomic.Int64{}
  225. upstreamToClientFrames := &atomic.Int64{}
  226. droppedDownstreamFrames := &atomic.Int64{}
  227. emitRelayTrace(onTrace, RelayTraceEvent{
  228. Stage: "relay_start",
  229. PayloadBytes: len(firstClientMessage),
  230. MessageType: relayMessageTypeString(firstMessageType),
  231. })
  232. if options.FirstMessageSent {
  233. emitRelayTrace(onTrace, RelayTraceEvent{
  234. Stage: "write_first_message_skipped",
  235. Direction: "client_to_upstream",
  236. MessageType: relayMessageTypeString(firstMessageType),
  237. PayloadBytes: len(firstClientMessage),
  238. })
  239. } else {
  240. if err := writeUpstream(firstMessageType, firstClientMessage); err != nil {
  241. result.Duration = nowFn().Sub(startAt)
  242. emitRelayTrace(onTrace, RelayTraceEvent{
  243. Stage: "write_first_message_failed",
  244. Direction: "client_to_upstream",
  245. MessageType: relayMessageTypeString(firstMessageType),
  246. PayloadBytes: len(firstClientMessage),
  247. Error: err.Error(),
  248. })
  249. return result, &RelayExit{Stage: "write_upstream", Err: err}
  250. }
  251. emitRelayTrace(onTrace, RelayTraceEvent{
  252. Stage: "write_first_message_ok",
  253. Direction: "client_to_upstream",
  254. MessageType: relayMessageTypeString(firstMessageType),
  255. PayloadBytes: len(firstClientMessage),
  256. })
  257. }
  258. clientToUpstreamFrames.Add(1)
  259. markActivity()
  260. exitCh := make(chan relayExitSignal, 3)
  261. dropDownstreamWrites := atomic.Bool{}
  262. clientReaderStarted := atomic.Bool{}
  263. startClientReader := func() {
  264. if !clientReaderStarted.CompareAndSwap(false, true) {
  265. return
  266. }
  267. go runClientToUpstream(relayCtx, clientConn, options.ReadClientFrame, writeClientFrameUpstream, markActivity, clientToUpstreamFrames, onTrace, exitCh)
  268. }
  269. if !options.StartClientAfterFirstDownstream {
  270. startClientReader()
  271. }
  272. upstreamDone := make(chan struct{})
  273. go func() {
  274. defer close(upstreamDone)
  275. runUpstreamToClient(
  276. relayCtx,
  277. upstreamConn,
  278. writeClient,
  279. startAt,
  280. nowFn,
  281. state,
  282. options.OnUsageParseFailure,
  283. options.OnTurnComplete,
  284. options.BeforeWriteClient,
  285. options.BeforeClientWrite,
  286. options.AfterClientWrite,
  287. func(msgType coderws.MessageType, payload []byte) {
  288. if options.StartClientAfterFirstDownstream {
  289. startClientReader()
  290. }
  291. },
  292. &dropDownstreamWrites,
  293. upstreamToClientFrames,
  294. droppedDownstreamFrames,
  295. markActivity,
  296. onTrace,
  297. exitCh,
  298. )
  299. }()
  300. go runIdleWatchdog(relayCtx, nowFn, options.IdleTimeout, &lastActivity, onTrace, exitCh)
  301. firstExit := <-exitCh
  302. // An outer ingress cancellation is a control-plane close, not a graceful
  303. // upstream disconnect. Leave the client connection open here so the
  304. // adapter can emit the precise lease/request close code. Internal
  305. // relayCancel does not cancel ctx and therefore does not take this path.
  306. if ctx.Err() != nil {
  307. firstExit.graceful = false
  308. }
  309. emitRelayTrace(onTrace, RelayTraceEvent{
  310. Stage: "first_exit",
  311. Direction: relayDirectionFromStage(firstExit.stage),
  312. Graceful: firstExit.graceful,
  313. WroteDownstream: firstExit.wroteDownstream,
  314. Error: relayErrorString(firstExit.err),
  315. })
  316. if options.BeforeRelayCancel != nil {
  317. options.BeforeRelayCancel(RelayExit{
  318. Stage: firstExit.stage,
  319. Err: firstExit.err,
  320. Graceful: firstExit.graceful,
  321. WroteDownstream: firstExit.wroteDownstream,
  322. })
  323. }
  324. combinedWroteDownstream := firstExit.wroteDownstream
  325. secondExit := relayExitSignal{graceful: true}
  326. hasSecondExit := false
  327. // 客户端断开后尽力继续读取上游短窗口,捕获延迟 usage/terminal 事件用于计费。
  328. if firstExit.stage == "read_client" && firstExit.graceful {
  329. dropDownstreamWrites.Store(true)
  330. secondExit, hasSecondExit = waitRelayExit(exitCh, drainTimeout)
  331. } else {
  332. relayCancel()
  333. _ = upstreamConn.Close()
  334. if clientReaderStarted.Load() {
  335. secondExit, hasSecondExit = waitRelayExit(exitCh, 200*time.Millisecond)
  336. }
  337. }
  338. if hasSecondExit {
  339. combinedWroteDownstream = combinedWroteDownstream || secondExit.wroteDownstream
  340. emitRelayTrace(onTrace, RelayTraceEvent{
  341. Stage: "second_exit",
  342. Direction: relayDirectionFromStage(secondExit.stage),
  343. Graceful: secondExit.graceful,
  344. WroteDownstream: secondExit.wroteDownstream,
  345. Error: relayErrorString(secondExit.err),
  346. })
  347. }
  348. relayCancel()
  349. _ = upstreamConn.Close()
  350. // ReadFrame observes relayCtx cancellation and Close is the transport-level
  351. // fallback. Join the reader before touching relayState or firing the final
  352. // turn callback; otherwise a late read can race Relay's result settlement.
  353. <-upstreamDone
  354. emitTurnComplete(options.OnTurnComplete, state, finalizePendingBareError(state, nowFn()))
  355. enrichResult(&result, state, nowFn().Sub(startAt))
  356. result.ClientToUpstreamFrames = clientToUpstreamFrames.Load()
  357. result.UpstreamToClientFrames = upstreamToClientFrames.Load()
  358. result.DroppedDownstreamFrames = droppedDownstreamFrames.Load()
  359. if options.FirstMessageSent && firstExit.stage == "read_client" && firstExit.graceful {
  360. emitRelayTrace(onTrace, RelayTraceEvent{
  361. Stage: "relay_client_closed",
  362. Graceful: true,
  363. WroteDownstream: combinedWroteDownstream,
  364. })
  365. return result, nil
  366. }
  367. if firstExit.stage == "read_client" && firstExit.graceful {
  368. stage := "client_disconnected"
  369. exitErr := firstExit.err
  370. if hasSecondExit && !secondExit.graceful {
  371. stage = secondExit.stage
  372. exitErr = secondExit.err
  373. }
  374. if exitErr == nil {
  375. exitErr = io.EOF
  376. }
  377. emitRelayTrace(onTrace, RelayTraceEvent{
  378. Stage: "relay_exit",
  379. Direction: relayDirectionFromStage(stage),
  380. Graceful: false,
  381. WroteDownstream: combinedWroteDownstream,
  382. Error: relayErrorString(exitErr),
  383. })
  384. return result, &RelayExit{
  385. Stage: stage,
  386. Err: exitErr,
  387. WroteDownstream: combinedWroteDownstream,
  388. }
  389. }
  390. if firstExit.graceful && (!hasSecondExit || secondExit.graceful) {
  391. emitRelayTrace(onTrace, RelayTraceEvent{
  392. Stage: "relay_complete",
  393. Graceful: true,
  394. WroteDownstream: combinedWroteDownstream,
  395. })
  396. _ = clientConn.Close()
  397. return result, nil
  398. }
  399. if !firstExit.graceful {
  400. emitRelayTrace(onTrace, RelayTraceEvent{
  401. Stage: "relay_exit",
  402. Direction: relayDirectionFromStage(firstExit.stage),
  403. Graceful: false,
  404. WroteDownstream: combinedWroteDownstream,
  405. Error: relayErrorString(firstExit.err),
  406. })
  407. return result, &RelayExit{
  408. Stage: firstExit.stage,
  409. Err: firstExit.err,
  410. WroteDownstream: combinedWroteDownstream,
  411. }
  412. }
  413. if hasSecondExit && !secondExit.graceful {
  414. emitRelayTrace(onTrace, RelayTraceEvent{
  415. Stage: "relay_exit",
  416. Direction: relayDirectionFromStage(secondExit.stage),
  417. Graceful: false,
  418. WroteDownstream: combinedWroteDownstream,
  419. Error: relayErrorString(secondExit.err),
  420. })
  421. return result, &RelayExit{
  422. Stage: secondExit.stage,
  423. Err: secondExit.err,
  424. WroteDownstream: combinedWroteDownstream,
  425. }
  426. }
  427. if options.FirstMessageSent {
  428. emitRelayTrace(onTrace, RelayTraceEvent{
  429. Stage: "relay_client_closed",
  430. Graceful: true,
  431. WroteDownstream: combinedWroteDownstream,
  432. })
  433. return result, nil
  434. }
  435. emitRelayTrace(onTrace, RelayTraceEvent{
  436. Stage: "relay_complete",
  437. Graceful: true,
  438. WroteDownstream: combinedWroteDownstream,
  439. })
  440. _ = clientConn.Close()
  441. return result, nil
  442. }
  443. func isClientResponseCreateFrame(msgType coderws.MessageType, payload []byte) bool {
  444. if msgType != coderws.MessageText && msgType != coderws.MessageBinary {
  445. return false
  446. }
  447. return strings.TrimSpace(gjson.GetBytes(payload, "type").String()) == "response.create"
  448. }
  449. func runClientToUpstream(
  450. ctx context.Context,
  451. clientConn FrameConn,
  452. readClientFrame func(context.Context, FrameConn) (coderws.MessageType, []byte, error),
  453. writeUpstream func(msgType coderws.MessageType, payload []byte) error,
  454. markActivity func(),
  455. forwardedFrames *atomic.Int64,
  456. onTrace func(event RelayTraceEvent),
  457. exitCh chan<- relayExitSignal,
  458. ) {
  459. if readClientFrame == nil {
  460. readClientFrame = func(ctx context.Context, conn FrameConn) (coderws.MessageType, []byte, error) {
  461. return conn.ReadFrame(ctx)
  462. }
  463. }
  464. for {
  465. msgType, payload, err := readClientFrame(ctx, clientConn)
  466. if err != nil {
  467. emitRelayTrace(onTrace, RelayTraceEvent{
  468. Stage: "read_client_failed",
  469. Direction: "client_to_upstream",
  470. Error: err.Error(),
  471. Graceful: isDisconnectError(err),
  472. })
  473. exitCh <- relayExitSignal{stage: "read_client", err: err, graceful: isDisconnectError(err)}
  474. return
  475. }
  476. markActivity()
  477. if err := writeUpstream(msgType, payload); err != nil {
  478. emitRelayTrace(onTrace, RelayTraceEvent{
  479. Stage: "write_upstream_failed",
  480. Direction: "client_to_upstream",
  481. MessageType: relayMessageTypeString(msgType),
  482. PayloadBytes: len(payload),
  483. Error: err.Error(),
  484. })
  485. exitCh <- relayExitSignal{stage: "write_upstream", err: err}
  486. return
  487. }
  488. if forwardedFrames != nil {
  489. forwardedFrames.Add(1)
  490. }
  491. markActivity()
  492. }
  493. }
  494. func runUpstreamToClient(
  495. ctx context.Context,
  496. upstreamConn FrameConn,
  497. writeClient func(msgType coderws.MessageType, payload []byte) error,
  498. startAt time.Time,
  499. nowFn func() time.Time,
  500. state *relayState,
  501. onUsageParseFailure func(eventType string, usageRaw string),
  502. onTurnComplete func(turn RelayTurnResult),
  503. beforeWriteClient func(msgType coderws.MessageType, payload []byte, wroteDownstream bool) error,
  504. beforeClientWrite func(msgType coderws.MessageType, payload []byte),
  505. afterClientWrite func(msgType coderws.MessageType, payload []byte, writeErr error),
  506. afterWriteClient func(msgType coderws.MessageType, payload []byte),
  507. dropDownstreamWrites *atomic.Bool,
  508. forwardedFrames *atomic.Int64,
  509. droppedFrames *atomic.Int64,
  510. markActivity func(),
  511. onTrace func(event RelayTraceEvent),
  512. exitCh chan<- relayExitSignal,
  513. ) {
  514. wroteDownstream := false
  515. for {
  516. msgType, payload, err := upstreamConn.ReadFrame(ctx)
  517. if err != nil {
  518. emitTurnComplete(onTurnComplete, state, finalizePendingBareError(state, nowFn()))
  519. graceful := isDisconnectError(err)
  520. // A clean WebSocket close only describes the transport handshake. Once
  521. // the upstream has started a Responses turn, success still requires a
  522. // terminal protocol event. Treat an early 1000/EOF as a relay failure so
  523. // the adapter does not report relay_completed with an active turn.
  524. if graceful && state.hasUnfinishedTurn() {
  525. graceful = false
  526. err = errors.New("upstream websocket closed before terminal event: " + err.Error())
  527. }
  528. emitRelayTrace(onTrace, RelayTraceEvent{
  529. Stage: "read_upstream_failed",
  530. Direction: "upstream_to_client",
  531. Error: err.Error(),
  532. Graceful: graceful,
  533. WroteDownstream: wroteDownstream,
  534. })
  535. exitCh <- relayExitSignal{
  536. stage: "read_upstream",
  537. err: err,
  538. graceful: graceful,
  539. wroteDownstream: wroteDownstream,
  540. }
  541. return
  542. }
  543. markActivity()
  544. if beforeWriteClient != nil {
  545. wroteDownstreamInTurn := wroteDownstream
  546. if state != nil {
  547. wroteDownstreamInTurn = state.turnWroteDownstream.Load()
  548. }
  549. if err := beforeWriteClient(msgType, payload, wroteDownstreamInTurn); err != nil {
  550. emitRelayTrace(onTrace, RelayTraceEvent{
  551. Stage: "upstream_message_rejected",
  552. Direction: "upstream_to_client",
  553. MessageType: relayMessageTypeString(msgType),
  554. PayloadBytes: len(payload),
  555. WroteDownstream: wroteDownstream,
  556. Error: err.Error(),
  557. })
  558. exitCh <- relayExitSignal{
  559. stage: "upstream_message",
  560. err: err,
  561. wroteDownstream: wroteDownstream,
  562. }
  563. return
  564. }
  565. }
  566. observedEvent := observedUpstreamEvent{}
  567. switch msgType {
  568. case coderws.MessageText:
  569. eventType := strings.TrimSpace(gjson.GetBytes(payload, "type").String())
  570. if shouldFinalizePendingBareError(state, payload, eventType) {
  571. emitTurnComplete(onTurnComplete, state, finalizePendingBareError(state, nowFn()))
  572. }
  573. observedEvent = observeUpstreamMessage(state, payload, startAt, nowFn, onUsageParseFailure)
  574. case coderws.MessageBinary:
  575. // Binary frames remain opaque for usage/result observation, but a JSON
  576. // terminal still settles relay lifecycle. Otherwise the pending-turn
  577. // disconnect guard would turn an already-delivered terminal into a false
  578. // missing-terminal failure when the upstream closes normally.
  579. if isTerminalEvent(strings.TrimSpace(gjson.GetBytes(payload, "type").String())) {
  580. state.consumePendingTurnStartedAt()
  581. openAIWSRelayDiscardActiveTurnTiming(state)
  582. }
  583. }
  584. emitTurnComplete(onTurnComplete, state, observedEvent)
  585. if dropDownstreamWrites != nil && dropDownstreamWrites.Load() {
  586. if droppedFrames != nil {
  587. droppedFrames.Add(1)
  588. }
  589. emitRelayTrace(onTrace, RelayTraceEvent{
  590. Stage: "drop_downstream_frame",
  591. Direction: "upstream_to_client",
  592. MessageType: relayMessageTypeString(msgType),
  593. PayloadBytes: len(payload),
  594. WroteDownstream: wroteDownstream,
  595. })
  596. if observedEvent.terminal {
  597. exitCh <- relayExitSignal{
  598. stage: "drain_terminal",
  599. graceful: true,
  600. wroteDownstream: wroteDownstream,
  601. }
  602. return
  603. }
  604. markActivity()
  605. continue
  606. }
  607. if beforeClientWrite != nil {
  608. beforeClientWrite(msgType, payload)
  609. }
  610. writeErr := writeClient(msgType, payload)
  611. if afterClientWrite != nil {
  612. afterClientWrite(msgType, payload, writeErr)
  613. }
  614. if writeErr != nil {
  615. emitRelayTrace(onTrace, RelayTraceEvent{
  616. Stage: "write_client_failed",
  617. Direction: "upstream_to_client",
  618. MessageType: relayMessageTypeString(msgType),
  619. PayloadBytes: len(payload),
  620. WroteDownstream: wroteDownstream,
  621. Error: writeErr.Error(),
  622. })
  623. exitCh <- relayExitSignal{stage: "write_client", err: writeErr, wroteDownstream: wroteDownstream}
  624. return
  625. }
  626. wroteDownstream = true
  627. if state != nil {
  628. state.turnWroteDownstream.Store(true)
  629. }
  630. if afterWriteClient != nil {
  631. afterWriteClient(msgType, payload)
  632. }
  633. if forwardedFrames != nil {
  634. forwardedFrames.Add(1)
  635. }
  636. markActivity()
  637. }
  638. }
  639. func runIdleWatchdog(
  640. ctx context.Context,
  641. nowFn func() time.Time,
  642. idleTimeout time.Duration,
  643. lastActivity *atomic.Int64,
  644. onTrace func(event RelayTraceEvent),
  645. exitCh chan<- relayExitSignal,
  646. ) {
  647. if idleTimeout <= 0 {
  648. return
  649. }
  650. checkInterval := minDuration(idleTimeout/4, 5*time.Second)
  651. if checkInterval < time.Second {
  652. checkInterval = time.Second
  653. }
  654. ticker := time.NewTicker(checkInterval)
  655. defer ticker.Stop()
  656. for {
  657. select {
  658. case <-ctx.Done():
  659. return
  660. case <-ticker.C:
  661. last := time.Unix(0, lastActivity.Load())
  662. if nowFn().Sub(last) < idleTimeout {
  663. continue
  664. }
  665. emitRelayTrace(onTrace, RelayTraceEvent{
  666. Stage: "idle_timeout_triggered",
  667. Direction: "watchdog",
  668. Error: context.DeadlineExceeded.Error(),
  669. })
  670. exitCh <- relayExitSignal{stage: "idle_timeout", err: context.DeadlineExceeded}
  671. return
  672. }
  673. }
  674. }
  675. func emitRelayTrace(onTrace func(event RelayTraceEvent), event RelayTraceEvent) {
  676. if onTrace == nil {
  677. return
  678. }
  679. onTrace(event)
  680. }
  681. func relayMessageTypeString(msgType coderws.MessageType) string {
  682. switch msgType {
  683. case coderws.MessageText:
  684. return "text"
  685. case coderws.MessageBinary:
  686. return "binary"
  687. default:
  688. return "unknown(" + strconv.Itoa(int(msgType)) + ")"
  689. }
  690. }
  691. func relayDirectionFromStage(stage string) string {
  692. switch stage {
  693. case "read_client", "write_upstream":
  694. return "client_to_upstream"
  695. case "read_upstream", "write_client", "drain_terminal":
  696. return "upstream_to_client"
  697. case "idle_timeout":
  698. return "watchdog"
  699. default:
  700. return ""
  701. }
  702. }
  703. func relayErrorString(err error) string {
  704. if err == nil {
  705. return ""
  706. }
  707. return err.Error()
  708. }
  709. func observeUpstreamMessage(
  710. state *relayState,
  711. message []byte,
  712. startAt time.Time,
  713. nowFn func() time.Time,
  714. onUsageParseFailure func(eventType string, usageRaw string),
  715. ) observedUpstreamEvent {
  716. if state == nil || len(message) == 0 {
  717. return observedUpstreamEvent{}
  718. }
  719. values := gjson.GetManyBytes(message, "type", "response.id", "response_id", "id")
  720. eventType := strings.TrimSpace(values[0].String())
  721. if eventType == "" {
  722. return observedUpstreamEvent{}
  723. }
  724. responseID := strings.TrimSpace(values[1].String())
  725. if responseID == "" {
  726. responseID = strings.TrimSpace(values[2].String())
  727. }
  728. // 仅 terminal 事件兜底读取顶层 id,避免把 event_id 当成 response_id 关联到 turn。
  729. if responseID == "" && isTerminalEvent(eventType) {
  730. responseID = strings.TrimSpace(values[3].String())
  731. }
  732. now := nowFn()
  733. if state.firstTokenMs == nil && isTokenEvent(eventType) {
  734. ms := int(now.Sub(startAt).Milliseconds())
  735. if ms >= 0 {
  736. state.firstTokenMs = &ms
  737. }
  738. if state.activeTurn != nil && state.activeTurn.firstTokenMs == nil {
  739. tms := int(now.Sub(state.activeTurn.startAt).Milliseconds())
  740. if tms >= 0 {
  741. state.activeTurn.firstTokenMs = &tms
  742. }
  743. }
  744. }
  745. parsedUsage := parseUsageAndAccumulate(state, message, eventType, onUsageParseFailure)
  746. observed := observedUpstreamEvent{
  747. eventType: eventType,
  748. responseID: responseID,
  749. usage: parsedUsage,
  750. }
  751. var turnTiming *relayTurnTiming
  752. if responseID != "" {
  753. turnTiming = openAIWSRelayGetOrInitTurnTiming(state, responseID, now)
  754. if turnTiming != nil && turnTiming.firstTokenMs == nil && isTokenEvent(eventType) {
  755. ms := int(now.Sub(turnTiming.startAt).Milliseconds())
  756. if ms >= 0 {
  757. turnTiming.firstTokenMs = &ms
  758. }
  759. }
  760. } else {
  761. turnTiming = state.activeTurn
  762. }
  763. observeRelayTurnResponseModel(turnTiming, firstRelayResponseModel(message), isTerminalEvent(eventType))
  764. if !isTerminalEvent(eventType) {
  765. return observed
  766. }
  767. observeRelayTurnResponseServiceTier(turnTiming, firstRelayResponseServiceTier(message))
  768. state.terminalEventType = eventType
  769. if eventType == "error" {
  770. // Some Responses servers emit error immediately before response.failed.
  771. // Defer turn settlement so the authoritative failed usage can replace
  772. // this fallback instead of billing both terminal frames.
  773. if observed.responseID == "" {
  774. observed.responseID = openAIWSRelayActiveTurnID(state)
  775. }
  776. pending := observed
  777. state.pendingBareError = &pending
  778. return observed
  779. }
  780. state.pendingBareError = nil
  781. return finalizeObservedRelayTerminal(state, observed, now)
  782. }
  783. func shouldFinalizePendingBareError(state *relayState, payload []byte, eventType string) bool {
  784. if state == nil || state.pendingBareError == nil {
  785. return false
  786. }
  787. eventType = strings.TrimSpace(eventType)
  788. if eventType == "" || eventType == "error" || eventType == "response.failed" {
  789. return false
  790. }
  791. if isTerminalEvent(eventType) || eventType == "response.created" {
  792. return true
  793. }
  794. // Auxiliary provider frames may be interleaved between error and its
  795. // authoritative response.failed. Only a response event identifying a
  796. // different turn closes the pending error.
  797. responseID := strings.TrimSpace(gjson.GetBytes(payload, "response.id").String())
  798. if responseID == "" || state.pendingBareError.responseID == "" {
  799. return false
  800. }
  801. return responseID != state.pendingBareError.responseID
  802. }
  803. func finalizePendingBareError(state *relayState, now time.Time) observedUpstreamEvent {
  804. if state == nil || state.pendingBareError == nil {
  805. return observedUpstreamEvent{}
  806. }
  807. observed := *state.pendingBareError
  808. state.pendingBareError = nil
  809. return finalizeObservedRelayTerminal(state, observed, now)
  810. }
  811. func finalizeObservedRelayTerminal(state *relayState, observed observedUpstreamEvent, now time.Time) observedUpstreamEvent {
  812. if state == nil || strings.TrimSpace(observed.eventType) == "" {
  813. return observedUpstreamEvent{}
  814. }
  815. observed.usage = finalizeRelayTurnUsage(state)
  816. observed.terminal = true
  817. responseID := strings.TrimSpace(observed.responseID)
  818. if responseID != "" {
  819. state.lastResponseID = responseID
  820. if turnTiming, ok := openAIWSRelayDeleteTurnTiming(state, responseID); ok {
  821. observed.responseModel = relayTurnResponseModel(&turnTiming)
  822. observed.responseConflict = turnTiming.responseModelConflict
  823. observed.responseServiceTier = turnTiming.terminalResponseServiceTier
  824. state.lastResponseModel = observed.responseModel
  825. state.responseConflict = observed.responseConflict
  826. state.lastResponseServiceTier = observed.responseServiceTier
  827. duration := now.Sub(turnTiming.startAt)
  828. if duration < 0 {
  829. duration = 0
  830. }
  831. observed.startedAt = turnTiming.startAt
  832. observed.duration = duration
  833. observed.firstToken = openAIWSRelayCloneIntPtr(turnTiming.firstTokenMs)
  834. }
  835. } else {
  836. state.consumePendingTurnStartedAt()
  837. openAIWSRelayDiscardActiveTurnTiming(state)
  838. }
  839. return observed
  840. }
  841. func emitTurnComplete(
  842. onTurnComplete func(turn RelayTurnResult),
  843. state *relayState,
  844. observed observedUpstreamEvent,
  845. ) {
  846. if onTurnComplete == nil || !observed.terminal {
  847. return
  848. }
  849. responseID := strings.TrimSpace(observed.responseID)
  850. if responseID == "" && strings.TrimSpace(observed.eventType) != "error" {
  851. return
  852. }
  853. requestModel := ""
  854. if state != nil {
  855. requestModel = state.currentRequestModel()
  856. }
  857. onTurnComplete(RelayTurnResult{
  858. RequestModel: requestModel,
  859. ResponseModel: observed.responseModel,
  860. ResponseModelConflict: observed.responseConflict,
  861. ResponseServiceTier: observed.responseServiceTier,
  862. Usage: observed.usage,
  863. RequestID: responseID,
  864. TerminalEventType: observed.eventType,
  865. StartedAt: observed.startedAt,
  866. Duration: observed.duration,
  867. FirstTokenMs: openAIWSRelayCloneIntPtr(observed.firstToken),
  868. })
  869. }
  870. func firstRelayResponseModel(message []byte) string {
  871. if len(message) == 0 {
  872. return ""
  873. }
  874. values := gjson.GetManyBytes(message, "response.model", "model")
  875. for _, value := range values {
  876. if value.Type != gjson.String {
  877. continue
  878. }
  879. if model := strings.TrimSpace(value.String()); model != "" {
  880. return model
  881. }
  882. }
  883. return ""
  884. }
  885. func observeRelayTurnResponseModel(turn *relayTurnTiming, model string, terminal bool) {
  886. if turn == nil {
  887. return
  888. }
  889. model = strings.TrimSpace(model)
  890. if model == "" {
  891. return
  892. }
  893. current := relayTurnResponseModel(turn)
  894. if current != "" && !strings.EqualFold(current, model) {
  895. turn.responseModelConflict = true
  896. }
  897. if terminal {
  898. turn.terminalResponseModel = model
  899. return
  900. }
  901. if turn.firstResponseModel == "" {
  902. turn.firstResponseModel = model
  903. }
  904. }
  905. func relayTurnResponseModel(turn *relayTurnTiming) string {
  906. if turn == nil {
  907. return ""
  908. }
  909. if turn.terminalResponseModel != "" {
  910. return turn.terminalResponseModel
  911. }
  912. return turn.firstResponseModel
  913. }
  914. func firstRelayResponseServiceTier(message []byte) string {
  915. if len(message) == 0 {
  916. return ""
  917. }
  918. values := gjson.GetManyBytes(message, "response.service_tier", "service_tier")
  919. for _, value := range values {
  920. if value.Type != gjson.String {
  921. continue
  922. }
  923. if tier := strings.TrimSpace(value.String()); tier != "" {
  924. return tier
  925. }
  926. }
  927. return ""
  928. }
  929. func observeRelayTurnResponseServiceTier(turn *relayTurnTiming, tier string) {
  930. if turn == nil {
  931. return
  932. }
  933. if tier = strings.TrimSpace(tier); tier != "" {
  934. turn.terminalResponseServiceTier = tier
  935. }
  936. }
  937. func openAIWSRelayGetOrInitTurnTiming(state *relayState, responseID string, now time.Time) *relayTurnTiming {
  938. if state == nil {
  939. return nil
  940. }
  941. if state.turnTimingByID == nil {
  942. state.turnTimingByID = make(map[string]*relayTurnTiming, 8)
  943. }
  944. timing, ok := state.turnTimingByID[responseID]
  945. if !ok || timing == nil || timing.startAt.IsZero() {
  946. startAt := state.consumePendingTurnStartedAt()
  947. if startAt.IsZero() {
  948. startAt = now
  949. }
  950. timing = &relayTurnTiming{startAt: startAt}
  951. state.turnTimingByID[responseID] = timing
  952. state.activeTurn = timing
  953. return timing
  954. }
  955. return timing
  956. }
  957. func (s *relayState) setPendingTurnStartedAt(startedAt time.Time) {
  958. if s == nil || startedAt.IsZero() {
  959. return
  960. }
  961. startedAtCopy := startedAt
  962. s.pendingTurnStart.Store(&startedAtCopy)
  963. }
  964. func (s *relayState) consumePendingTurnStartedAt() time.Time {
  965. if s == nil {
  966. return time.Time{}
  967. }
  968. startedAt := s.pendingTurnStart.Swap(nil)
  969. if startedAt == nil {
  970. return time.Time{}
  971. }
  972. return *startedAt
  973. }
  974. func (s *relayState) hasUnfinishedTurn() bool {
  975. if s == nil {
  976. return false
  977. }
  978. return s.pendingTurnStart.Load() != nil || s.activeTurn != nil
  979. }
  980. func openAIWSRelayDeleteTurnTiming(state *relayState, responseID string) (relayTurnTiming, bool) {
  981. if state == nil || state.turnTimingByID == nil {
  982. return relayTurnTiming{}, false
  983. }
  984. timing, ok := state.turnTimingByID[responseID]
  985. if !ok || timing == nil {
  986. return relayTurnTiming{}, false
  987. }
  988. delete(state.turnTimingByID, responseID)
  989. if state.activeTurn == timing {
  990. state.activeTurn = nil
  991. }
  992. return *timing, true
  993. }
  994. func openAIWSRelayDiscardActiveTurnTiming(state *relayState) {
  995. if state == nil || state.activeTurn == nil {
  996. return
  997. }
  998. active := state.activeTurn
  999. for responseID, timing := range state.turnTimingByID {
  1000. if timing == active {
  1001. delete(state.turnTimingByID, responseID)
  1002. }
  1003. }
  1004. state.activeTurn = nil
  1005. }
  1006. func openAIWSRelayActiveTurnID(state *relayState) string {
  1007. if state == nil || state.activeTurn == nil {
  1008. return ""
  1009. }
  1010. for responseID, timing := range state.turnTimingByID {
  1011. if timing == state.activeTurn {
  1012. return responseID
  1013. }
  1014. }
  1015. return ""
  1016. }
  1017. func openAIWSRelayCloneIntPtr(v *int) *int {
  1018. if v == nil {
  1019. return nil
  1020. }
  1021. cloned := *v
  1022. return &cloned
  1023. }
  1024. func parseUsageAndAccumulate(
  1025. state *relayState,
  1026. message []byte,
  1027. eventType string,
  1028. onParseFailure func(eventType string, usageRaw string),
  1029. ) Usage {
  1030. if state == nil || len(message) == 0 || !shouldParseUsage(eventType) || !bytes.Contains(message, []byte(`"usage"`)) {
  1031. return Usage{}
  1032. }
  1033. usageResult := gjson.GetBytes(message, "response.usage")
  1034. if !usageResult.Exists() {
  1035. usageResult = gjson.GetBytes(message, "usage")
  1036. }
  1037. if !usageResult.Exists() {
  1038. return Usage{}
  1039. }
  1040. usageRaw := strings.TrimSpace(usageResult.Raw)
  1041. if usageRaw == "" || !strings.HasPrefix(usageRaw, "{") {
  1042. recordUsageParseFailure()
  1043. if onParseFailure != nil {
  1044. onParseFailure(eventType, usageRaw)
  1045. }
  1046. return Usage{}
  1047. }
  1048. inputResult := usageResult.Get("input_tokens")
  1049. if !inputResult.Exists() {
  1050. inputResult = usageResult.Get("prompt_tokens")
  1051. }
  1052. outputResult := usageResult.Get("output_tokens")
  1053. if !outputResult.Exists() {
  1054. outputResult = usageResult.Get("completion_tokens")
  1055. }
  1056. cachedResult := usageResult.Get("input_tokens_details.cached_tokens")
  1057. if !cachedResult.Exists() {
  1058. cachedResult = usageResult.Get("prompt_tokens_details.cached_tokens")
  1059. }
  1060. imageTokens := usageResult.Get("output_tokens_details.image_tokens").Int()
  1061. if imageTokens == 0 {
  1062. imageTokens = usageResult.Get("completion_tokens_details.image_tokens").Int()
  1063. }
  1064. requireTotals := isTerminalEvent(strings.TrimSpace(eventType))
  1065. inputTokens, inputOK := parseUsageIntField(inputResult, requireTotals)
  1066. outputTokens, outputOK := parseUsageIntField(outputResult, requireTotals)
  1067. cachedTokens, cachedOK := parseUsageIntField(cachedResult, false)
  1068. if !inputOK || !outputOK || !cachedOK {
  1069. recordUsageParseFailure()
  1070. if onParseFailure != nil {
  1071. onParseFailure(eventType, usageRaw)
  1072. }
  1073. // 解析失败时不做部分字段累加,避免计费 usage 出现“半有效”状态。
  1074. return Usage{}
  1075. }
  1076. reasoningTokens := usageResult.Get("output_tokens_details.reasoning_tokens").Int()
  1077. if reasoningTokens == 0 {
  1078. reasoningTokens = usageResult.Get("completion_tokens_details.reasoning_tokens").Int()
  1079. }
  1080. if reasoningTokens > 0 {
  1081. outputTokens = int(xai.IncludeIndependentReasoningTokens(
  1082. int64(inputTokens), int64(outputTokens), usageResult.Get("total_tokens").Int(), reasoningTokens,
  1083. ))
  1084. }
  1085. parsedUsage := Usage{
  1086. InputTokens: inputTokens,
  1087. OutputTokens: outputTokens,
  1088. CacheCreationInputTokens: openAICacheCreationTokensFromUsage(usageResult),
  1089. CacheReadInputTokens: cachedTokens,
  1090. ImageOutputTokens: int(imageTokens),
  1091. }
  1092. if isTerminalEvent(strings.TrimSpace(eventType)) {
  1093. if relayUsageHasTokens(parsedUsage) || !relayUsageHasTokens(state.turnUsage) {
  1094. state.turnUsage = parsedUsage
  1095. }
  1096. } else {
  1097. mergeRelayUsageNonZero(&state.turnUsage, parsedUsage)
  1098. return Usage{}
  1099. }
  1100. return parsedUsage
  1101. }
  1102. func relayUsageHasTokens(usage Usage) bool {
  1103. return usage.InputTokens > 0 || usage.OutputTokens > 0 ||
  1104. usage.CacheCreationInputTokens > 0 || usage.CacheReadInputTokens > 0 ||
  1105. usage.ImageOutputTokens > 0
  1106. }
  1107. func mergeRelayUsageNonZero(dst *Usage, src Usage) {
  1108. if dst == nil {
  1109. return
  1110. }
  1111. if src.InputTokens > 0 {
  1112. dst.InputTokens = src.InputTokens
  1113. }
  1114. if src.OutputTokens > 0 {
  1115. dst.OutputTokens = src.OutputTokens
  1116. }
  1117. if src.CacheCreationInputTokens > 0 {
  1118. dst.CacheCreationInputTokens = src.CacheCreationInputTokens
  1119. }
  1120. if src.CacheReadInputTokens > 0 {
  1121. dst.CacheReadInputTokens = src.CacheReadInputTokens
  1122. }
  1123. if src.ImageOutputTokens > 0 {
  1124. dst.ImageOutputTokens = src.ImageOutputTokens
  1125. }
  1126. }
  1127. func finalizeRelayTurnUsage(state *relayState) Usage {
  1128. if state == nil {
  1129. return Usage{}
  1130. }
  1131. turnUsage := state.turnUsage
  1132. state.usage.InputTokens += turnUsage.InputTokens
  1133. state.usage.OutputTokens += turnUsage.OutputTokens
  1134. state.usage.CacheCreationInputTokens += turnUsage.CacheCreationInputTokens
  1135. state.usage.CacheReadInputTokens += turnUsage.CacheReadInputTokens
  1136. state.usage.ImageOutputTokens += turnUsage.ImageOutputTokens
  1137. state.turnUsage = Usage{}
  1138. return turnUsage
  1139. }
  1140. func parseUsageIntField(value gjson.Result, required bool) (int, bool) {
  1141. if !value.Exists() {
  1142. return 0, !required
  1143. }
  1144. if value.Type != gjson.Number {
  1145. return 0, false
  1146. }
  1147. return int(value.Int()), true
  1148. }
  1149. func openAICacheCreationTokensFromUsage(value gjson.Result) int {
  1150. for _, field := range []string{
  1151. "input_tokens_details.cache_write_tokens",
  1152. "prompt_tokens_details.cache_write_tokens",
  1153. "input_tokens_details.cache_creation_tokens",
  1154. "prompt_tokens_details.cache_creation_tokens",
  1155. } {
  1156. result := value.Get(field)
  1157. if result.Exists() {
  1158. return max(int(result.Int()), 0)
  1159. }
  1160. }
  1161. for _, field := range []string{
  1162. "cache_write_tokens",
  1163. "cache_creation_input_tokens",
  1164. "cache_write_input_tokens",
  1165. "cache_creation_tokens",
  1166. } {
  1167. if tokens := int(value.Get(field).Int()); tokens > 0 {
  1168. return tokens
  1169. }
  1170. }
  1171. return 0
  1172. }
  1173. func enrichResult(result *RelayResult, state *relayState, duration time.Duration) {
  1174. if result == nil {
  1175. return
  1176. }
  1177. result.Duration = duration
  1178. if state == nil {
  1179. return
  1180. }
  1181. result.RequestModel = state.currentRequestModel()
  1182. result.ResponseModel = state.lastResponseModel
  1183. result.ResponseModelConflict = state.responseConflict
  1184. result.ResponseServiceTier = state.lastResponseServiceTier
  1185. result.Usage = state.usage
  1186. result.RequestID = state.lastResponseID
  1187. result.TerminalEventType = state.terminalEventType
  1188. result.FirstTokenMs = state.firstTokenMs
  1189. }
  1190. func (s *relayState) setRequestModel(model string) {
  1191. if s == nil || model == "" {
  1192. return
  1193. }
  1194. s.requestModelMu.Lock()
  1195. s.requestModel = model
  1196. s.requestModelMu.Unlock()
  1197. }
  1198. func (s *relayState) currentRequestModel() string {
  1199. if s == nil {
  1200. return ""
  1201. }
  1202. s.requestModelMu.RLock()
  1203. defer s.requestModelMu.RUnlock()
  1204. return s.requestModel
  1205. }
  1206. func isDisconnectError(err error) bool {
  1207. if err == nil {
  1208. return false
  1209. }
  1210. if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) || errors.Is(err, context.Canceled) {
  1211. return true
  1212. }
  1213. switch coderws.CloseStatus(err) {
  1214. case coderws.StatusNormalClosure, coderws.StatusGoingAway, coderws.StatusNoStatusRcvd, coderws.StatusAbnormalClosure:
  1215. return true
  1216. }
  1217. message := strings.ToLower(strings.TrimSpace(err.Error()))
  1218. if message == "" {
  1219. return false
  1220. }
  1221. return strings.Contains(message, "failed to read frame header: eof") ||
  1222. strings.Contains(message, "unexpected eof") ||
  1223. strings.Contains(message, "use of closed network connection") ||
  1224. strings.Contains(message, "connection reset by peer") ||
  1225. strings.Contains(message, "broken pipe")
  1226. }
  1227. func isTerminalEvent(eventType string) bool {
  1228. switch strings.TrimSpace(eventType) {
  1229. case "response.completed", "response.done", "response.failed", "response.incomplete", "response.cancelled", "response.canceled", "error":
  1230. return true
  1231. default:
  1232. return false
  1233. }
  1234. }
  1235. func shouldParseUsage(eventType string) bool {
  1236. eventType = strings.TrimSpace(eventType)
  1237. if eventType == "error" || isTerminalEvent(eventType) {
  1238. return true
  1239. }
  1240. return strings.HasPrefix(eventType, "response.") && !strings.HasSuffix(eventType, ".delta")
  1241. }
  1242. func isTokenEvent(eventType string) bool {
  1243. eventType = strings.TrimSpace(eventType)
  1244. return strings.HasSuffix(eventType, ".delta") ||
  1245. eventType == "response.output_text.done" ||
  1246. eventType == "response.function_call_arguments.done"
  1247. }
  1248. func minDuration(a, b time.Duration) time.Duration {
  1249. if a <= 0 {
  1250. return b
  1251. }
  1252. if b <= 0 {
  1253. return a
  1254. }
  1255. if a < b {
  1256. return a
  1257. }
  1258. return b
  1259. }
  1260. func waitRelayExit(exitCh <-chan relayExitSignal, timeout time.Duration) (relayExitSignal, bool) {
  1261. if timeout <= 0 {
  1262. timeout = 200 * time.Millisecond
  1263. }
  1264. select {
  1265. case sig := <-exitCh:
  1266. return sig, true
  1267. case <-time.After(timeout):
  1268. return relayExitSignal{}, false
  1269. }
  1270. }