67 lines
1.8 KiB
Go
67 lines
1.8 KiB
Go
package app
|
|
|
|
import (
|
|
"errors"
|
|
"unicode/utf8"
|
|
|
|
"gorm.io/gorm"
|
|
"infinite-canvas/backend/internal/model"
|
|
"infinite-canvas/backend/internal/repository"
|
|
)
|
|
|
|
// Publish upstream deltas to the same durable, revisioned transcript used by
|
|
// Agent SSE/replay. Each flush re-reads state; never overwrite a scheduler's
|
|
// checkpoint with the snapshot from when the model request started.
|
|
func newCloudAgentStreamPublisher(s *Service, userID, taskID, kind string) *taskTextStreamPublisher {
|
|
p := newTaskTextStreamPublisher(s, userID, taskID)
|
|
written := 0
|
|
p.sink = func(delta string) error {
|
|
remaining := 32000 - written
|
|
if remaining <= 0 {
|
|
return nil
|
|
}
|
|
if len(delta) > remaining {
|
|
delta = delta[:remaining]
|
|
for !utf8.ValidString(delta) {
|
|
delta = delta[:len(delta)-1]
|
|
}
|
|
}
|
|
if delta == "" {
|
|
return nil
|
|
}
|
|
for attempt := 0; attempt < 3; attempt++ {
|
|
run, err := s.repo.CloudAgentForActiveTask(userID, taskID)
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = s.repo.MutateCloudAgent(userID, run.ID, run.Revision, func(current *model.CloudAgentExecution, _ *repository.Repository) error {
|
|
state, err := cloudAgentDecode(current)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if state.ActiveTaskID != taskID || (current.Status != "running" && current.Status != "queued") {
|
|
return nil
|
|
}
|
|
messageID := taskID
|
|
if kind == "reasoning_delta" {
|
|
messageID += ":reasoning"
|
|
}
|
|
state.event(run.ID, kind, map[string]any{"messageId": messageID, "text": delta})
|
|
return cloudAgentSave(current, &state)
|
|
})
|
|
if errors.Is(err, repository.ErrCreationConflict) {
|
|
continue
|
|
}
|
|
if err == nil {
|
|
written += len(delta)
|
|
}
|
|
return err
|
|
}
|
|
return repository.ErrCreationConflict
|
|
}
|
|
return p
|
|
}
|