Decouple stream cancellation and clarify failover rules

This commit is contained in:
2026-05-12 21:32:23 +08:00
parent 98abd247d6
commit 682a491d27
8 changed files with 629 additions and 17 deletions
+49 -3
View File
@@ -1,6 +1,7 @@
package httpapi
import (
"context"
"encoding/json"
"errors"
"fmt"
@@ -564,10 +565,15 @@ func (s *Server) createTask(kind string, compatible bool) http.Handler {
writeTaskAccepted(w, task)
return
}
runCtx, cancelRun := s.requestExecutionContext(r)
defer cancelRun()
if compatible {
if boolValue(body, "stream") {
flusher := prepareCompatibleStream(w)
result, runErr := s.runner.ExecuteStream(r.Context(), task, user, func(delta string) error {
result, runErr := s.runner.ExecuteStream(runCtx, task, user, func(delta string) error {
if !requestStillConnected(r) {
return nil
}
writeCompatibleDelta(w, kind, model, delta)
if flusher != nil {
flusher.Flush()
@@ -575,6 +581,9 @@ func (s *Server) createTask(kind string, compatible bool) http.Handler {
return nil
})
if runErr != nil {
if !requestStillConnected(r) {
return
}
status := statusFromRunError(runErr)
errorPayload := map[string]any{
"code": runErrorCode(runErr),
@@ -593,29 +602,66 @@ func (s *Server) createTask(kind string, compatible bool) http.Handler {
}
return
}
if !requestStillConnected(r) {
return
}
writeCompatibleDone(w, kind, model, result.Output)
if flusher != nil {
flusher.Flush()
}
return
}
result, runErr := s.runner.Execute(r.Context(), task, user)
result, runErr := s.runner.Execute(runCtx, task, user)
if runErr != nil {
if !requestStillConnected(r) {
return
}
writeError(w, statusFromRunError(runErr), runErr.Error(), runErrorCode(runErr))
return
}
if !requestStillConnected(r) {
return
}
writeJSON(w, http.StatusOK, result.Output)
return
}
result, runErr := s.runner.Execute(r.Context(), task, user)
result, runErr := s.runner.Execute(runCtx, task, user)
if runErr != nil {
s.logger.Warn("task completed with failure", "kind", kind, "taskId", task.ID, "error", runErr)
}
if !requestStillConnected(r) {
return
}
writeTaskAccepted(w, result.Task)
})
}
func (s *Server) requestExecutionContext(r *http.Request) (context.Context, context.CancelFunc) {
base := context.WithoutCancel(r.Context())
if s.ctx == nil {
return base, func() {}
}
ctx, cancel := context.WithCancel(base)
go func() {
select {
case <-s.ctx.Done():
cancel()
case <-ctx.Done():
}
}()
return ctx, cancel
}
func requestStillConnected(r *http.Request) bool {
select {
case <-r.Context().Done():
return false
default:
return true
}
}
func asyncRequest(r *http.Request) bool {
value := strings.TrimSpace(strings.ToLower(r.Header.Get("x-async")))
return value == "1" || value == "true" || value == "yes" || value == "on"