okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
7fd8cf5
main
1package daemon 2 3import ( 4"encoding/json" 5"errors" 6"unicode/utf8" 7 8"albedo/cli/internal/daemon/protocol" 9) 10 11// ToolActivity records display-only execution evidence, not model-facing tool output. 12type ToolActivity struct { 13Kind string `json:"kind"` // "read" | "search" | "list" | "run" | "web" 14Target string `json:"target"` 15// Detail is the markdown a web search answered with; empty otherwise. 16Detail string `json:"detail,omitempty"` 17} 18 19type FileChange struct { 20Path string `json:"path"` 21Kind string `json:"kind"` // "diff" | "unavailable" 22Diff string `json:"diff,omitempty"` 23Reason string `json:"reason,omitempty"` 24Added int `json:"added,omitempty"` 25Removed int `json:"removed,omitempty"` 26} 27 28type ToolTrace struct { 29Activities []ToolActivity `json:"activities"` 30Changes []FileChange `json:"changes"` 31Truncated bool `json:"truncated,omitempty"` 32} 33 34// ParseToolTrace validates the display evidence encoded in a history or tool event. 35func ParseToolTrace (data json.RawMessage )* ToolTrace { 36if len (data )== 0 { 37return nil 38 } 39 40var trace ToolTrace 41if err := json .Unmarshal (data ,& trace );err != nil { 42return nil 43 } 44 45if len (trace .Activities )> 64 || len (trace .Changes )> 16 { 46return nil 47 } 48 49for _ ,act := range trace .Activities { 50switch act .Kind { 51case "read" ,"search" ,"list" ,"run" ,"web" : 52default : 53return nil 54 } 55// Python bounds these strings by Unicode characters, not UTF-8 bytes. 56if utf8 .RuneCountInString (act .Target )> 1000 || utf8 .RuneCountInString (act .Detail )> 16000 { 57return nil 58 } 59 } 60 61for _ ,ch := range trace .Changes { 62if utf8 .RuneCountInString (ch .Path )> 1000 { 63return nil 64 } 65switch ch .Kind { 66case "unavailable" : 67if utf8 .RuneCountInString (ch .Reason )> 1000 { 68return nil 69 } 70case "diff" : 71if utf8 .RuneCountInString (ch .Diff )> 16000 || ch .Added < 0 || ch .Removed < 0 { 72return nil 73 } 74default : 75return nil 76 } 77 } 78 79return & trace 80} 81 82// ToolCodePreview contains the latest bounded window of decoded source code. 83type ToolCodePreview struct { 84Text string `json:"text"` 85Offset int `json:"offset"` 86} 87 88type ToolProgress struct { 89Code * ToolCodePreview `json:"code,omitempty"` 90ToolCallID string `json:"tool_call_id,omitempty"` 91CallID string `json:"call_id"` 92Name string `json:"name"` 93Phase string `json:"phase"` // "generating" | "running" 94} 95 96const maxActiveToolProgress = 32 97 98type EventType string 99 100const ( 101EventReset EventType = "reset" 102EventRetry EventType = "retry" 103EventText EventType = "text" 104EventError EventType = "error" 105EventInterrupted EventType = "interrupted" 106EventMessage EventType = "message" 107EventUser EventType = "user" 108EventTurnMembership EventType = "turn_membership" 109EventTurnCompleted EventType = "turn_completed" 110EventThinking EventType = "thinking" 111EventNote EventType = "note" 112EventCompacted EventType = "compacted" 113EventToolProgress EventType = "tool_progress" 114EventTool EventType = "tool" 115EventUsage EventType = "usage" 116EventInvalidate EventType = "invalidate" 117// EventCommitted says transcript rows up to Seq now cover what was shown. 118EventCommitted EventType = "committed" 119) 120 121type Usage struct { 122Model string `json:"model,omitempty"` 123RecordedAt * int64 `json:"recordedAt,omitempty"` 124PromptTokens * int `json:"promptTokens,omitempty"` 125CachedPromptTokens * int `json:"cachedPromptTokens,omitempty"` 126CacheWriteTokens * int `json:"cacheWriteTokens,omitempty"` 127CompletionTokens * int `json:"completionTokens,omitempty"` 128TotalTokens * int `json:"totalTokens,omitempty"` 129ElapsedMs * float64 `json:"elapsedMs,omitempty"` 130TokensPerSecond * float64 `json:"tokensPerSecond,omitempty"` 131// CacheFade is how CachedPromptTokens fades once the session goes quiet, 132// in time order. 133CacheFade []CacheStep `json:"cacheFade,omitempty"` 134} 135 136// CacheStep says that from At (unix ms) on, a request would read Cached 137// tokens from the provider's cache; nil when that is no longer known. 138type CacheStep struct { 139Cached * int `json:"cached,omitempty"` 140At int64 `json:"at"` 141} 142 143type StreamEvent struct { 144MessageID string 145ReplacesAllLive bool 146ReplacesLiveIDs []string 147EntryID string 148Position int64 149TurnType string 150Speaker string 151MailKind string 152SenderSessionID string 153Invalidation * ResourceInvalidation 154Status * AgentStatus 155Snapshot * Session 156Receipt * InputReceipt 157OperationID string 158Images []ImageMetadata 159Usage * Usage 160ToolTrace * ToolTrace 161Timestamp * int64 162ToolArgs map [string ]any 163Progress * ToolProgress 164TurnID string 165Type EventType 166Text string 167Source string 168Summary string 169Strategy string 170ToolName string 171ToolResult string 172ProgressCallID string 173SubmissionIDs []string 174Evicted int 175// ElapsedMs is how long a replayed thought took, when the daemon timed it. 176ElapsedMs int64 177ElapsedObserved bool 178// Seq is the newest transcript row an EventCommitted covers. 179Seq int64 180// Before and More describe the history a reset or page carried: Before is 181// its first row (the cursor for the next older page), More whether one exists. 182Before int64 183More bool 184// Replayed marks an event from the transcript snapshot that follows a 185// reset: history, which says nothing about what the session does now. 186Replayed bool 187} 188 189type ResourceInvalidation struct { 190Session ,Settings ,Catalog ,Context ,Extension bool 191} 192 193// decodeToolProgress validates the normalized progress object before delivery. 194// A null value clears the current live progress. 195func decodeToolProgress (raw json.RawMessage ) (* ToolProgress ,error ) { 196if string (raw )== "null" { 197return nil ,nil 198 } 199if len (raw )> 8192 { 200return nil ,errors .New ("tool progress exceeds 8 KiB" ) 201 } 202var wire protocol.ToolProgress 203if err := decodeRequired (raw ,& wire );err != nil { 204return nil ,err 205 } 206return validateToolProgress (wire ) 207} 208 209func validateToolProgress (wire protocol.ToolProgress ) (* ToolProgress ,error ) { 210if wire .CallID == "" || wire .Name == "" || len (wire .Name )> 100 || (wire .Phase != "generating" && wire .Phase != "running" ) { 211return nil ,fieldError ("tool progress" ) 212 } 213preview := value (wire .Preview ) 214if preview .OffsetScalars < 0 || utf8 .RuneCountInString (preview .Text )> 512 || len (preview .Text )> 2048 { 215return nil ,fieldError ("tool progress preview" ) 216 } 217return progressValue (wire ),nil 218} 219func progressValue (wire protocol.ToolProgress )* ToolProgress { 220progress := & ToolProgress {CallID :wire .CallID ,ToolCallID :value (wire .ToolCallID ),Name :wire .Name ,Phase :wire .Phase } 221preview := value (wire .Preview ) 222if preview .Text != "" || preview .OffsetScalars > 0 { 223progress .Code = & ToolCodePreview {Text :preview .Text ,Offset :int (preview .OffsetScalars )} 224 } 225return progress 226} 227func usageValue (wire protocol.Usage )* Usage { 228usage := & Usage {Model :value (wire .Model ),PromptTokens :intPointer (wire .PromptTokens ),CachedPromptTokens :intPointer (wire .CachedPromptTokens ),CacheWriteTokens :intPointer (wire .CacheWriteTokens ),CompletionTokens :intPointer (wire .CompletionTokens ),TotalTokens :intPointer (wire .TotalTokens ),ElapsedMs :wire .ElapsedMs ,TokensPerSecond :wire .TokensPerSecond } 229if wire .ObservedAt != nil { 230usage .RecordedAt = timestampMilliseconds (* wire .ObservedAt ) 231 } 232for _ ,step := range wire .CacheFade { 233if at := timestampMilliseconds (step .At );at != nil { 234usage .CacheFade = append (usage .CacheFade ,CacheStep {At :* at ,Cached :intPointer (step .CachedTokens )}) 235 } 236 } 237return usage 238}