sse.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. package agentserver
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. )
  6. // Resource 会话下的子资源(run / 交互 / 文件),代理据此校验不带会话 ID 的请求
  7. type Resource struct {
  8. Id string
  9. SessionId string
  10. }
  11. // ResourcesFromEvent 从一条事件中提取子资源 ID:
  12. // - run.started 等带 runId 的事件 → runId
  13. // - interaction.required → payload.interactionId
  14. // - file.changed → payload.fileId
  15. func ResourcesFromEvent(e AgentEvent) []Resource {
  16. if e.SessionId == "" {
  17. return nil
  18. }
  19. var out []Resource
  20. if e.Type == "run.started" && e.RunId != nil && *e.RunId != "" {
  21. out = append(out, Resource{Id: *e.RunId, SessionId: e.SessionId})
  22. }
  23. switch e.Type {
  24. case "interaction.required":
  25. var p struct {
  26. InteractionId string `json:"interactionId"`
  27. }
  28. if json.Unmarshal(e.Payload, &p) == nil && p.InteractionId != "" {
  29. out = append(out, Resource{Id: p.InteractionId, SessionId: e.SessionId})
  30. }
  31. case "file.changed":
  32. var p struct {
  33. FileId string `json:"fileId"`
  34. }
  35. if json.Unmarshal(e.Payload, &p) == nil && p.FileId != "" {
  36. out = append(out, Resource{Id: p.FileId, SessionId: e.SessionId})
  37. }
  38. }
  39. return out
  40. }
  41. // SSEScanner 增量解析 SSE 字节流,对每个完整事件的 data 回调一次。
  42. // 只用于旁路提取资源 ID,不影响原样转发;单个事件超过 maxEventBytes 时丢弃该事件。
  43. type SSEScanner struct {
  44. buf []byte
  45. onData func(data []byte)
  46. }
  47. const maxEventBytes = 4 << 20
  48. // NewSSEScanner 创建解析器
  49. func NewSSEScanner(onData func(data []byte)) *SSEScanner {
  50. return &SSEScanner{onData: onData}
  51. }
  52. // Write 输入一段原始字节
  53. func (s *SSEScanner) Write(p []byte) {
  54. s.buf = append(s.buf, p...)
  55. for {
  56. idx, sepLen := eventBoundary(s.buf)
  57. if idx < 0 {
  58. break
  59. }
  60. s.emit(s.buf[:idx])
  61. s.buf = s.buf[idx+sepLen:]
  62. }
  63. if len(s.buf) > maxEventBytes {
  64. s.buf = s.buf[:0]
  65. }
  66. }
  67. // eventBoundary 查找事件分隔(空行),兼容 \n\n 与 \r\n\r\n
  68. func eventBoundary(b []byte) (int, int) {
  69. i := bytes.Index(b, []byte("\n\n"))
  70. j := bytes.Index(b, []byte("\r\n\r\n"))
  71. switch {
  72. case i < 0 && j < 0:
  73. return -1, 0
  74. case j < 0 || (i >= 0 && i < j):
  75. return i, 2
  76. default:
  77. return j, 4
  78. }
  79. }
  80. func (s *SSEScanner) emit(block []byte) {
  81. var data []byte
  82. for _, line := range bytes.Split(block, []byte("\n")) {
  83. line = bytes.TrimSuffix(line, []byte("\r"))
  84. if bytes.HasPrefix(line, []byte("data:")) {
  85. v := bytes.TrimPrefix(line, []byte("data:"))
  86. v = bytes.TrimPrefix(v, []byte(" "))
  87. if data != nil {
  88. data = append(data, '\n')
  89. }
  90. data = append(data, v...)
  91. }
  92. }
  93. if data != nil && s.onData != nil {
  94. s.onData(data)
  95. }
  96. }
  97. // ResourcesFromSSEData 解析一条 SSE data(事件 JSON)
  98. func ResourcesFromSSEData(data []byte) []Resource {
  99. var e AgentEvent
  100. if err := json.Unmarshal(data, &e); err != nil {
  101. return nil
  102. }
  103. return ResourcesFromEvent(e)
  104. }
  105. // ResourcesFromResponse 从普通 JSON 响应中提取子资源 ID,sessionId 为路径中的会话
  106. func ResourcesFromResponse(action Action, sessionId string, body []byte) []Resource {
  107. var out []Resource
  108. switch action {
  109. case ActionSendMessage:
  110. var r RunDto
  111. if json.Unmarshal(body, &r) == nil && r.Id != "" {
  112. out = append(out, Resource{Id: r.Id, SessionId: sessionId})
  113. }
  114. case ActionUploadFiles, ActionListFiles:
  115. var fs []FileDto
  116. if json.Unmarshal(body, &fs) == nil {
  117. for _, f := range fs {
  118. if f.Id != "" {
  119. out = append(out, Resource{Id: f.Id, SessionId: sessionId})
  120. }
  121. }
  122. }
  123. case ActionEvents:
  124. var page EventsPage
  125. if json.Unmarshal(body, &page) == nil {
  126. for _, e := range page.Items {
  127. out = append(out, ResourcesFromEvent(e)...)
  128. }
  129. }
  130. case ActionSessionDetail:
  131. var d SessionDetailDto
  132. if json.Unmarshal(body, &d) == nil {
  133. if d.ActiveRunId != nil && *d.ActiveRunId != "" {
  134. out = append(out, Resource{Id: *d.ActiveRunId, SessionId: sessionId})
  135. }
  136. for _, it := range d.PendingInteractions {
  137. if it.InteractionId != "" {
  138. out = append(out, Resource{Id: it.InteractionId, SessionId: sessionId})
  139. }
  140. }
  141. }
  142. case ActionInteractions:
  143. var items []InteractionRefDto
  144. if json.Unmarshal(body, &items) == nil {
  145. for _, it := range items {
  146. if it.InteractionId != "" {
  147. out = append(out, Resource{Id: it.InteractionId, SessionId: sessionId})
  148. }
  149. }
  150. }
  151. }
  152. // 只保留与路径会话一致的资源,防止伪造的事件写入他人会话的索引
  153. filtered := out[:0]
  154. for _, r := range out {
  155. if r.SessionId == sessionId {
  156. filtered = append(filtered, r)
  157. }
  158. }
  159. return filtered
  160. }