| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- package api
-
- import (
- "bufio"
- "bytes"
- "encoding/json"
- "fmt"
- "log"
- "net/http"
- "strings"
- "time"
-
- "git.x2erp.com/qdy/go-svc-code/internal/opencode/container"
- )
-
- // EventsStreamHandler 创建OpenCode事件流代理处理器,包含会话过滤
- func EventsStreamHandler(manager *container.InstanceManager) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- // 从URL路径中提取项目ID
- // 路径格式:/api/opencode/projects/{id}/events
- pathParts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
- if len(pathParts) < 5 {
- http.Error(w, "无效的路径格式", http.StatusBadRequest)
- return
- }
-
- projectID := pathParts[3]
- // 从查询参数获取会话ID
- sessionID := r.URL.Query().Get("session_id")
- if sessionID == "" {
- sessionID = "default"
- }
-
- log.Printf("OpenCode事件流请求: 项目ID=%s, 会话ID=%s", projectID, sessionID)
-
- // 获取实例
- instance := manager.GetInstance(projectID)
- if instance == nil {
- http.Error(w, fmt.Sprintf("项目 %s 的OpenCode实例未启动", projectID), http.StatusNotFound)
- return
- }
-
- if instance.Status != container.StatusRunning {
- http.Error(w, fmt.Sprintf("项目 %s 的OpenCode实例不在运行状态", projectID), http.StatusServiceUnavailable)
- return
- }
-
- // 构建OpenCode事件流URL
- eventsURL := fmt.Sprintf("http://localhost:%d/api/sessions/%s/events", instance.Port, sessionID)
-
- // 创建HTTP请求到OpenCode实例
- req, err := http.NewRequest("GET", eventsURL, nil)
- if err != nil {
- http.Error(w, fmt.Sprintf("创建HTTP请求失败: %v", err), http.StatusInternalServerError)
- return
- }
-
- // 复制原始请求的头
- for name, values := range r.Header {
- for _, value := range values {
- req.Header.Add(name, value)
- }
- }
-
- // 添加OpenCode认证头(如果需要)
- if instance.Token != "" {
- req.Header.Set("Authorization", "Bearer "+instance.Token)
- }
-
- // 发送请求
- client := &http.Client{}
- resp, err := client.Do(req)
- if err != nil {
- http.Error(w, fmt.Sprintf("连接到OpenCode事件流失败: %v", err), http.StatusBadGateway)
- return
- }
- defer resp.Body.Close()
-
- // 检查响应状态
- if resp.StatusCode != http.StatusOK {
- http.Error(w, fmt.Sprintf("OpenCode事件流返回错误状态码: %d", resp.StatusCode), http.StatusBadGateway)
- return
- }
-
- // 设置响应头
- w.Header().Set("Content-Type", "text/event-stream")
- w.Header().Set("Cache-Control", "no-cache")
- w.Header().Set("Connection", "keep-alive")
- w.Header().Set("Access-Control-Allow-Origin", "*")
-
- flusher, ok := w.(http.Flusher)
- if !ok {
- http.Error(w, "流式响应不支持", http.StatusInternalServerError)
- return
- }
-
- // 建立连接事件
- connectEvent := map[string]interface{}{
- "type": "connected",
- "project_id": projectID,
- "session_id": sessionID,
- "timestamp": timestamp(),
- }
- sendSSEEvent(w, flusher, connectEvent)
-
- // 使用bufio.Scanner读取SSE事件流
- scanner := bufio.NewScanner(resp.Body)
- scanner.Buffer(make([]byte, 64*1024), 1024*1024) // 增加缓冲区大小
-
- var eventBuffer bytes.Buffer
- var inEvent bool
-
- for scanner.Scan() {
- line := scanner.Text()
-
- if line == "" {
- // 空行表示事件结束
- if inEvent && eventBuffer.Len() > 0 {
- data := eventBuffer.String()
- eventBuffer.Reset()
- inEvent = false
-
- // 处理SSE事件
- processedEvent := processSSEEvent(projectID, sessionID, data)
- if processedEvent != "" {
- // 发送处理后的事件
- fmt.Fprintf(w, "%s\n", processedEvent)
- flusher.Flush()
- }
- }
- continue
- }
-
- if !inEvent {
- inEvent = true
- }
-
- // 累积事件行
- eventBuffer.WriteString(line)
- eventBuffer.WriteString("\n")
- }
-
- if err := scanner.Err(); err != nil {
- log.Printf("读取OpenCode事件流失败: 项目=%s, 会话=%s, 错误=%v", projectID, sessionID, err)
- }
- }
- }
-
- // processSSEEvent 处理SSE事件,添加项目ID和会话ID信息
- func processSSEEvent(projectID, sessionID, data string) string {
- // 解析SSE事件格式:data: {...}
- lines := strings.Split(data, "\n")
-
- for _, line := range lines {
- if strings.HasPrefix(line, "data: ") {
- eventData := strings.TrimPrefix(line, "data: ")
-
- // 尝试解析JSON
- var eventObj map[string]interface{}
- if err := json.Unmarshal([]byte(eventData), &eventObj); err != nil {
- // 如果不是JSON,直接返回原始数据
- return fmt.Sprintf("data: %s\n", eventData)
- }
-
- // 添加项目ID和会话ID信息
- eventObj["project_id"] = projectID
- eventObj["session_id"] = sessionID
- eventObj["timestamp"] = timestamp()
-
- // 重新序列化
- processedData, err := json.Marshal(eventObj)
- if err != nil {
- log.Printf("序列化处理后的事件失败: %v", err)
- return fmt.Sprintf("data: %s\n", eventData)
- }
-
- return fmt.Sprintf("data: %s\n", string(processedData))
- }
- }
-
- return data
- }
-
- // sendSSEEvent 发送SSE事件
- func sendSSEEvent(w http.ResponseWriter, flusher http.Flusher, event map[string]interface{}) {
- eventJSON, err := json.Marshal(event)
- if err != nil {
- log.Printf("序列化SSE事件失败: %v", err)
- return
- }
-
- fmt.Fprintf(w, "data: %s\n\n", string(eventJSON))
- flusher.Flush()
- }
-
- // timestamp 获取当前时间戳
- func timestamp() string {
- return fmt.Sprintf("%d", time.Now().UnixNano()/int64(time.Millisecond))
- }
|