okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
a229862
main
1package daemon 2 3import ( 4"encoding/json" 5"errors" 6"strings" 7 8"albedo/cli/internal/daemon/protocol" 9) 10 11type wireChatEvent struct { 12CallID string 13StreamEvent 14} 15type sessionEventEnvelope struct { 16Type string `json:"type"` 17Sequence * int64 `json:"sequence"` 18Data json.RawMessage `json:"data"` 19} 20 21func decodeChatEvent (raw json.RawMessage ) (* wireChatEvent ,error ) { 22var envelope sessionEventEnvelope 23if err := decodeRequired (raw ,& envelope ,"type" ,"data" );err != nil { 24return nil ,err 25 } 26return decodeChatEnvelope (envelope ,len (raw ),nil ) 27} 28 29func decodeChatEnvelope (envelope sessionEventEnvelope ,wireBytes int ,content * eventContentReader ) (* wireChatEvent ,error ) { 30if envelope .Type == "" || envelope .Data == nil || string (envelope .Data )== "null" { 31return nil ,fieldError ("event" ) 32 } 33if envelope .Type != "reset" && envelope .Type != "failure" && (envelope .Sequence == nil || * envelope .Sequence < 1 ) { 34return nil ,fieldError ("event sequence" ) 35 } 36event := StreamEvent {Type :EventType (envelope .Type )} 37switch envelope .Type { 38case "reset" : 39var data struct { 40Reason string `json:"reason"` 41 } 42if err := decodeRequired (envelope .Data ,& data ,"reason" );err != nil { 43return nil ,err 44 } 45switch data .Reason { 46case "initial" ,"generation_changed" ,"replay_unavailable" ,"recovery" : 47default : 48return nil ,fieldError ("reset reason" ) 49 } 50case "status" : 51var data protocol.SessionStatus 52if err := decodeRequired (envelope .Data ,& data );err != nil { 53return nil ,err 54 } 55if err := validateSessionStatus (data );err != nil { 56return nil ,err 57 } 58status := statusValue (data , protocol.Kernel {}) 59event .Status = & status 60case "input" : 61var data struct { 62Input protocol.Input `json:"input"` 63 } 64if err := decodeRequired (envelope .Data ,& data ,"input" );err != nil { 65return nil ,err 66 } 67if err := validInput (data .Input );err != nil { 68return nil ,err 69 } 70receipt := InputReceipt (data .Input ) 71event .Receipt = & receipt 72event .OperationID = data .Input .ID 73if data .Input .Turn != nil { 74event .Type = EventTurnMembership 75event .TurnID = data .Input .Turn .ID 76event .SubmissionIDs = []string {data .Input .ID } 77 } 78case "text" ,"thinking" : 79var data struct { 80RunID string `json:"run_id"` 81MessageID string `json:"message_id"` 82ElapsedMs * int64 `json:"elapsed_ms"` 83Text string `json:"text"` 84 } 85if err := decodeRequired (envelope .Data ,& data ,"run_id" ,"message_id" ,"text" );err != nil { 86return nil ,err 87 } 88if data .RunID == "" || data .MessageID == "" { 89return nil ,fieldError ("message identity" ) 90 } 91event .Text ,event .TurnID ,event .MessageID = data .Text ,data .RunID ,data .MessageID 92if data .ElapsedMs != nil { 93event .ElapsedMs = * data .ElapsedMs 94event .ElapsedObserved = true 95 } 96case "message" : 97var data struct { 98Entry protocol.HistoryEntry `json:"entry"` 99 } 100if err := decodeRequired (envelope .Data ,& data ,"entry" );err != nil { 101return nil ,err 102 } 103if content != nil { 104entry ,err := content .hydrateEntry (data .Entry ) 105if err != nil { 106return nil ,classifyStreamFailure (err ) 107 } 108data .Entry = entry 109 } 110entries ,err := historyEntryEvents (data .Entry ) 111if err != nil { 112return nil ,err 113 } 114if len (entries )== 0 { 115return nil ,nil 116 } 117event = entries [0 ] 118event .Replayed = false 119case "note" : 120var data struct { 121Text string `json:"text"` 122EntryID string `json:"entry_id"` 123Origin string `json:"origin"` 124* protocol.MailMetadata `json:"mail"` 125 } 126if err := decodeRequired (envelope .Data ,& data ,"entry_id" ,"origin" ,"text" ,"mail" );err != nil { 127return nil ,err 128 } 129event .Text ,event .Source = data .Text ,data .Origin 130case "tool_progress" : 131if wireBytes > 8192 { 132return nil ,errors .New ("tool_progress exceeds 8 KiB" ) 133 } 134var data struct { 135Progress json.RawMessage `json:"progress"` 136 } 137if err := decodeRequired (envelope .Data ,& data ,"progress" );err != nil { 138return nil ,err 139 } 140progress ,err := decodeToolProgress (data .Progress ) 141if err != nil { 142return nil ,err 143 } 144event .Progress = progress 145case "tool" : 146var data toolEventData 147if err := decodeRequired (envelope .Data ,& data ,"tool_call_id" ,"progress_call_id" ,"name" ,"arguments" ,"result" ,"trace" ,"content_complete" ,"reference" );err != nil { 148return nil ,err 149 } 150if data .ToolCallID == "" || data .ProgressCallID == "" || data .Name == "" { 151return nil ,fieldError ("tool identity" ) 152 } 153if content != nil { 154if err := content .hydrateTool (& data );err != nil { 155return nil ,classifyStreamFailure (err ) 156 } 157 } 158event .ToolName ,event .ProgressCallID = data .Name ,data .ProgressCallID 159arguments ,_ := dynamicValue (data .Arguments ) 160event .ToolArgs ,_ = arguments .(map [string ]any ) 161if json .Unmarshal (data .Result ,& event .ToolResult )!= nil { 162event .ToolResult = string (data .Result ) 163 } 164event .ToolTrace = ParseToolTrace (data .Trace ) 165return & wireChatEvent {CallID :data .ToolCallID ,StreamEvent :event },nil 166case "usage" : 167var data protocol.Usage 168if err := decodeRequired (envelope .Data ,& data );err != nil { 169return nil ,err 170 } 171event .Usage = usageValue (data ) 172case "committed" : 173var data struct { 174HighWater int64 `json:"high_water"` 175ReplacesLiveIDs []string `json:"replaces_live_ids"` 176ReplacesAllLive bool `json:"replaces_all_live"` 177 } 178if err := decodeRequired (envelope .Data ,& data ,"high_water" ,"replaces_live_ids" ,"replaces_all_live" );err != nil { 179return nil ,err 180 } 181if data .HighWater < 0 { 182return nil ,fieldError ("history high water" ) 183 } 184if data .ReplacesLiveIDs == nil || len (data .ReplacesLiveIDs )> 256 { 185return nil ,fieldError ("live replacement identities" ) 186 } 187for _ ,id := range data .ReplacesLiveIDs { 188if id == "" { 189return nil ,fieldError ("live replacement identity" ) 190 } 191 } 192event .Seq ,event .ReplacesLiveIDs = data .HighWater ,data .ReplacesLiveIDs 193event .ReplacesAllLive = data .ReplacesAllLive 194case "turn_completed" : 195var data struct { 196RunID string `json:"run_id"` 197State string `json:"state"` 198InputIDs []string `json:"input_ids"` 199InputCount int64 `json:"input_count"` 200Truncated bool `json:"truncated"` 201 } 202if err := decodeRequired (envelope .Data ,& data ,"run_id" ,"state" ,"input_ids" ,"input_count" ,"truncated" );err != nil { 203return nil ,err 204 } 205if data .RunID == "" || data .InputIDs == nil { 206return nil ,fieldError ("turn completion" ) 207 } 208event .TurnID ,event .SubmissionIDs = data .RunID ,data .InputIDs 209if data .State == "interrupted" { 210event .Source = "interrupted" 211 }else if data .State == "failed" || data .State == "abandoned" { 212event .Source = "failed" 213 }else if data .State != "completed" { 214return nil ,fieldError ("turn state" ) 215 } 216case "retry" : 217var data struct { 218RunID string `json:"run_id"` 219Reason protocol.SafeReason `json:"reason"` 220 } 221if err := decodeRequired (envelope .Data ,& data ,"run_id" ,"attempt" ,"reason" ,"delay_ms" );err != nil { 222return nil ,err 223 } 224event .TurnID ,event .Text = data .RunID ,data .Reason .Detail 225case "compacted" : 226var data protocol.CompactionObservation 227if err := json .Unmarshal (envelope .Data ,& data );err != nil { 228return nil ,err 229 } 230event .Evicted = int (data .EvictedEntries ) 231event .Summary = data .Summary 232event .Strategy = value (data .Strategy ) 233case "error" : 234var data struct { 235RunID * string `json:"run_id"` 236Code string `json:"code"` 237Message string `json:"message"` 238 } 239if err := decodeRequired (envelope .Data ,& data ,"run_id" ,"code" ,"message" );err != nil { 240return nil ,err 241 } 242if data .Code == "history_publication_failed" { 243return nil ,& APIError {StatusCode :503 ,Code :data .Code ,Message :data .Message } 244 } 245event .Text ,event .TurnID = data .Message ,value (data .RunID ) 246case "invalidate" : 247var data struct { 248Kind string `json:"kind"` 249URL string `json:"url"` 250 } 251if err := decodeRequired (envelope .Data ,& data ,"kind" ,"url" );err != nil { 252return nil ,err 253 } 254var resources ResourceInvalidation 255switch data .Kind { 256case "session" : 257resources .Session = true 258case "settings" : 259resources .Settings = true 260case "catalog" : 261resources .Catalog = true 262case "context" : 263resources .Context = true 264case "extension" : 265resources .Extension = true 266default : 267return nil ,fieldError ("invalidation kind" ) 268 } 269if ! strings .HasPrefix (data .URL ,"/" )|| strings .HasPrefix (data .URL ,"//" ) { 270return nil ,fieldError ("invalidation URL" ) 271 } 272event .Invalidation = & resources 273case "failure" : 274return nil ,errors .New ("failure must be a terminal batch" ) 275default : 276return nil ,nil 277 } 278return & wireChatEvent {StreamEvent :event },nil 279} 280 281type toolEventData struct { 282Reference * protocol.ContentReference `json:"reference"` 283ToolCallID string `json:"tool_call_id"` 284ProgressCallID string `json:"progress_call_id"` 285Name string `json:"name"` 286Arguments json.RawMessage `json:"arguments"` 287Result json.RawMessage `json:"result"` 288Trace json.RawMessage `json:"trace"` 289ContentComplete bool `json:"content_complete"` 290}