okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
a229862
main
1package daemon 2 3import ( 4"bufio" 5"context" 6"encoding/json" 7"errors" 8"io" 9"net/url" 10"os" 11"strings" 12"unicode/utf8" 13 14"albedo/cli/internal/daemon/protocol" 15) 16 17func validateActiveOutput (session string ,outputs []protocol.ActiveOutput )error { 18if outputs == nil || len (outputs )> 256 { 19return fieldError ("active output" ) 20 } 21seen := map [string ]bool {} 22var total int64 23inlineBytes := 0 24for _ ,output := range outputs { 25if output .MessageID == "" || output .RunID == "" || seen [output .MessageID ]|| output .Bytes < 0 || (output .Kind != "text" && output .Kind != "thinking" )|| (output .Text == nil )== (output .Reference == nil )|| output .ElapsedMs != nil && * output .ElapsedMs < 0 { 26return fieldError ("active output descriptor" ) 27 } 28seen [output .MessageID ]= true 29total += output .Bytes 30if output .Bytes > 64 * 1024 * 1024 || total > 64 * 1024 * 1024 { 31return fieldError ("active output size" ) 32 } 33if output .Text != nil { 34encoded ,_ := json .Marshal (* output .Text ) 35inlineBytes += len (encoded ) 36if inlineBytes > 64 * 1024 { 37return fieldError ("active output inline size" ) 38 } 39if ! utf8 .ValidString (* output .Text )|| int64 (len (* output .Text ))!= output .Bytes { 40return fieldError ("active output text" ) 41 } 42continue 43 } 44reference := output .Reference 45parsed ,err := url .Parse (reference .URL ) 46prefix := "/sessions/" + url .PathEscape (session )+ "/active-output/" 47if err != nil || parsed .IsAbs ()|| parsed .Host != "" || parsed .Fragment != "" || ! strings .HasPrefix (parsed .Path ,prefix )|| strings .Contains (strings .TrimPrefix (parsed .Path ,prefix ),"/" )|| strings .TrimPrefix (parsed .Path ,prefix )== "" || parsed .Query ().Get ("snapshot" )== "" || reference .Field != output .Kind || reference .Bytes != output .Bytes { 48return fieldError ("active output reference" ) 49 } 50 } 51return nil 52} 53 54type stagedActiveOutput struct { 55output protocol.ActiveOutput 56offset int64 57} 58type activeOutputStage struct { 59file * os.File 60outputs []stagedActiveOutput 61} 62 63func (stage * activeOutputStage )close () { 64if stage != nil && stage .file != nil { 65name := stage .file .Name () 66_ = stage .file .Close () 67_ = os .Remove (name ) 68 } 69} 70 71// Validate and stage complete prefixes before replacing any visible state. Large 72// prefixes stay on disk rather than accumulating in the pending event batch. 73func stageActiveOutput (ctx context.Context ,conn * Connection ,outputs []protocol.ActiveOutput ) (* activeOutputStage ,error ) { 74stage := & activeOutputStage {} 75if len (outputs )== 0 { 76return stage ,nil 77 } 78offset := int64 (0 ) 79for _ ,output := range outputs { 80stage .outputs = append (stage .outputs ,stagedActiveOutput {output :output ,offset :offset }) 81if output .Reference == nil { 82continue 83 } 84if stage .file == nil { 85file ,err := os .CreateTemp ("" ,"albedo-active-output-*" ) 86if err != nil { 87return nil ,err 88 } 89stage .file = file 90 } 91if err := stage .readReference (ctx ,conn ,output );err != nil { 92stage .close () 93return nil ,err 94 } 95offset += output .Bytes 96 } 97return stage ,nil 98} 99func (stage * activeOutputStage )readReference (ctx context.Context ,conn * Connection ,output protocol.ActiveOutput )error { 100reference := output .Reference 101route ,_ := url .Parse (reference .URL ) 102contentID := route .Path [strings .LastIndex (route .Path ,"/" )+ 1 :] 103offset := int64 (0 ) 104complete := false 105return walkPages (func (next * string ) (protocol.EntryContentPage ,* string ,error ) { 106query := route .Query () 107if next != nil { 108query .Set ("next" ,* next ) 109 } 110route .RawQuery = query .Encode () 111var page protocol.EntryContentPage 112err := executeRead (ctx ,conn ,operation {Name :"read active output" ,Path :route .String (),Policy :readRecovery },func (data []byte )error {return decodeRequired (data ,& page ) }) 113return page ,page .Next ,err 114 },func (page protocol.EntryContentPage )error { 115if page .EntryID != contentID || page .Parts == nil || len (page .Parts )== 0 || complete { 116return fieldError ("active output content identity" ) 117 } 118for _ ,part := range page .Parts { 119if part .Field != output .Kind || part .Encoding != "utf8" || part .OffsetBytes != offset || ! utf8 .ValidString (part .Text )|| complete || part .Text == "" && ! part .Complete { 120return fieldError ("active output content offset" ) 121 } 122offset += int64 (len (part .Text )) 123if offset > output .Bytes { 124return fieldError ("active output content cutoff" ) 125 } 126if _ ,err := io .WriteString (stage .file ,part .Text );err != nil { 127return err 128 } 129complete = part .Complete 130 } 131if page .Next == nil && (! complete || offset != output .Bytes )|| page .Next != nil && complete { 132return fieldError ("active output content completeness" ) 133 } 134return nil 135 },"repeated active output page" ) 136} 137func (stage * activeOutputStage )deliver (ctx context.Context ,onEvent func (StreamEvent )error )error { 138if stage == nil { 139return nil 140 } 141for _ ,item := range stage .outputs { 142var input io.Reader 143if item .output .Text != nil { 144input = strings .NewReader (* item .output .Text ) 145 }else { 146input = io .NewSectionReader (stage .file ,item .offset ,item .output .Bytes ) 147 } 148reader := bufio .NewReader (input ) 149var chunk strings.Builder 150emit := func (final bool )error { 151event := StreamEvent {Type :EventType (item .output .Kind ),Text :chunk .String (),MessageID :item .output .MessageID ,TurnID :item .output .RunID } 152if final && item .output .ElapsedMs != nil { 153event .ElapsedMs = * item .output .ElapsedMs 154event .ElapsedObserved = true 155 } 156if err := onEvent (event );err != nil { 157return err 158 } 159chunk .Reset () 160return nil 161 } 162for { 163if err := ctx .Err ();err != nil { 164return err 165 } 166char ,size ,err := reader .ReadRune () 167if errors .Is (err ,io .EOF ) { 168break 169 } 170if err != nil { 171return err 172 } 173if char == utf8 .RuneError && size == 1 { 174return fieldError ("active output UTF-8" ) 175 } 176if chunk .Len ()+ size > 64 * 1024 { 177if err := emit (false );err != nil { 178return err 179 } 180 } 181chunk .WriteRune (char ) 182 } 183if err := emit (true );err != nil { 184return err 185 } 186 } 187return nil 188}