events.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. package server
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log/slog"
  6. "github.com/charmbracelet/crush/internal/agent/notify"
  7. "github.com/charmbracelet/crush/internal/agent/tools/mcp"
  8. "github.com/charmbracelet/crush/internal/app"
  9. "github.com/charmbracelet/crush/internal/history"
  10. "github.com/charmbracelet/crush/internal/message"
  11. "github.com/charmbracelet/crush/internal/permission"
  12. "github.com/charmbracelet/crush/internal/proto"
  13. "github.com/charmbracelet/crush/internal/pubsub"
  14. "github.com/charmbracelet/crush/internal/session"
  15. )
  16. // wrapEvent converts a raw tea.Msg (a pubsub.Event[T] from the app
  17. // event fan-in) into a pubsub.Payload envelope with the correct
  18. // PayloadType discriminator and a proto-typed inner payload that has
  19. // proper JSON tags. Returns nil if the event type is unrecognized.
  20. func wrapEvent(ev any) *pubsub.Payload {
  21. switch e := ev.(type) {
  22. case pubsub.Event[app.LSPEvent]:
  23. return envelope(pubsub.PayloadTypeLSPEvent, pubsub.Event[proto.LSPEvent]{
  24. Type: e.Type,
  25. Payload: proto.LSPEvent{
  26. Type: proto.LSPEventType(e.Payload.Type),
  27. Name: e.Payload.Name,
  28. State: e.Payload.State,
  29. Error: e.Payload.Error,
  30. DiagnosticCount: e.Payload.DiagnosticCount,
  31. },
  32. })
  33. case pubsub.Event[mcp.Event]:
  34. return envelope(pubsub.PayloadTypeMCPEvent, pubsub.Event[proto.MCPEvent]{
  35. Type: e.Type,
  36. Payload: proto.MCPEvent{
  37. Type: mcpEventTypeToProto(e.Payload.Type),
  38. Name: e.Payload.Name,
  39. State: proto.MCPState(e.Payload.State),
  40. Error: e.Payload.Error,
  41. ToolCount: e.Payload.Counts.Tools,
  42. },
  43. })
  44. case pubsub.Event[permission.PermissionRequest]:
  45. return envelope(pubsub.PayloadTypePermissionRequest, pubsub.Event[proto.PermissionRequest]{
  46. Type: e.Type,
  47. Payload: proto.PermissionRequest{
  48. ID: e.Payload.ID,
  49. SessionID: e.Payload.SessionID,
  50. ToolCallID: e.Payload.ToolCallID,
  51. ToolName: e.Payload.ToolName,
  52. Description: e.Payload.Description,
  53. Action: e.Payload.Action,
  54. Path: e.Payload.Path,
  55. Params: e.Payload.Params,
  56. },
  57. })
  58. case pubsub.Event[permission.PermissionNotification]:
  59. return envelope(pubsub.PayloadTypePermissionNotification, pubsub.Event[proto.PermissionNotification]{
  60. Type: e.Type,
  61. Payload: proto.PermissionNotification{
  62. ToolCallID: e.Payload.ToolCallID,
  63. Granted: e.Payload.Granted,
  64. Denied: e.Payload.Denied,
  65. },
  66. })
  67. case pubsub.Event[message.Message]:
  68. return envelope(pubsub.PayloadTypeMessage, pubsub.Event[proto.Message]{
  69. Type: e.Type,
  70. Payload: messageToProto(e.Payload),
  71. })
  72. case pubsub.Event[session.Session]:
  73. return envelope(pubsub.PayloadTypeSession, pubsub.Event[proto.Session]{
  74. Type: e.Type,
  75. Payload: sessionToProto(e.Payload),
  76. })
  77. case pubsub.Event[history.File]:
  78. return envelope(pubsub.PayloadTypeFile, pubsub.Event[proto.File]{
  79. Type: e.Type,
  80. Payload: fileToProto(e.Payload),
  81. })
  82. case pubsub.Event[notify.Notification]:
  83. return envelope(pubsub.PayloadTypeAgentEvent, pubsub.Event[proto.AgentEvent]{
  84. Type: e.Type,
  85. Payload: proto.AgentEvent{
  86. SessionID: e.Payload.SessionID,
  87. SessionTitle: e.Payload.SessionTitle,
  88. Type: proto.AgentEventType(e.Payload.Type),
  89. },
  90. })
  91. default:
  92. slog.Warn("Unrecognized event type for SSE wrapping", "type", fmt.Sprintf("%T", ev))
  93. return nil
  94. }
  95. }
  96. // envelope marshals the inner event and wraps it in a pubsub.Payload.
  97. func envelope(payloadType pubsub.PayloadType, inner any) *pubsub.Payload {
  98. raw, err := json.Marshal(inner)
  99. if err != nil {
  100. slog.Error("Failed to marshal event payload", "error", err)
  101. return nil
  102. }
  103. return &pubsub.Payload{
  104. Type: payloadType,
  105. Payload: raw,
  106. }
  107. }
  108. func mcpEventTypeToProto(t mcp.EventType) proto.MCPEventType {
  109. switch t {
  110. case mcp.EventStateChanged:
  111. return proto.MCPEventStateChanged
  112. case mcp.EventToolsListChanged:
  113. return proto.MCPEventToolsListChanged
  114. case mcp.EventPromptsListChanged:
  115. return proto.MCPEventPromptsListChanged
  116. case mcp.EventResourcesListChanged:
  117. return proto.MCPEventResourcesListChanged
  118. default:
  119. return proto.MCPEventStateChanged
  120. }
  121. }
  122. func sessionToProto(s session.Session) proto.Session {
  123. return proto.Session{
  124. ID: s.ID,
  125. ParentSessionID: s.ParentSessionID,
  126. Title: s.Title,
  127. SummaryMessageID: s.SummaryMessageID,
  128. MessageCount: s.MessageCount,
  129. PromptTokens: s.PromptTokens,
  130. CompletionTokens: s.CompletionTokens,
  131. Cost: s.Cost,
  132. CreatedAt: s.CreatedAt,
  133. UpdatedAt: s.UpdatedAt,
  134. }
  135. }
  136. func fileToProto(f history.File) proto.File {
  137. return proto.File{
  138. ID: f.ID,
  139. SessionID: f.SessionID,
  140. Path: f.Path,
  141. Content: f.Content,
  142. Version: f.Version,
  143. CreatedAt: f.CreatedAt,
  144. UpdatedAt: f.UpdatedAt,
  145. }
  146. }
  147. func messageToProto(m message.Message) proto.Message {
  148. msg := proto.Message{
  149. ID: m.ID,
  150. SessionID: m.SessionID,
  151. Role: proto.MessageRole(m.Role),
  152. Model: m.Model,
  153. Provider: m.Provider,
  154. CreatedAt: m.CreatedAt,
  155. UpdatedAt: m.UpdatedAt,
  156. }
  157. for _, p := range m.Parts {
  158. switch v := p.(type) {
  159. case message.TextContent:
  160. msg.Parts = append(msg.Parts, proto.TextContent{Text: v.Text})
  161. case message.ReasoningContent:
  162. msg.Parts = append(msg.Parts, proto.ReasoningContent{
  163. Thinking: v.Thinking,
  164. Signature: v.Signature,
  165. StartedAt: v.StartedAt,
  166. FinishedAt: v.FinishedAt,
  167. })
  168. case message.ToolCall:
  169. msg.Parts = append(msg.Parts, proto.ToolCall{
  170. ID: v.ID,
  171. Name: v.Name,
  172. Input: v.Input,
  173. Finished: v.Finished,
  174. })
  175. case message.ToolResult:
  176. msg.Parts = append(msg.Parts, proto.ToolResult{
  177. ToolCallID: v.ToolCallID,
  178. Name: v.Name,
  179. Content: v.Content,
  180. IsError: v.IsError,
  181. })
  182. case message.Finish:
  183. msg.Parts = append(msg.Parts, proto.Finish{
  184. Reason: proto.FinishReason(v.Reason),
  185. Time: v.Time,
  186. Message: v.Message,
  187. Details: v.Details,
  188. })
  189. case message.ImageURLContent:
  190. msg.Parts = append(msg.Parts, proto.ImageURLContent{URL: v.URL, Detail: v.Detail})
  191. case message.BinaryContent:
  192. msg.Parts = append(msg.Parts, proto.BinaryContent{Path: v.Path, MIMEType: v.MIMEType, Data: v.Data})
  193. }
  194. }
  195. return msg
  196. }
  197. func messagesToProto(msgs []message.Message) []proto.Message {
  198. out := make([]proto.Message, len(msgs))
  199. for i, m := range msgs {
  200. out[i] = messageToProto(m)
  201. }
  202. return out
  203. }