okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
6e82811
main
1package daemon 2 3import ( 4"context" 5"encoding/json" 6"io" 7"net/http" 8 9"albedo/cli/internal/daemon/protocol" 10) 11 12type SendResult struct { 13OperationID string `json:"operationId"` 14OK bool `json:"ok"` 15Queued bool `json:"queued"` 16AcceptanceOrder int64 17} 18 19// SubmissionRequest keeps message text and continuation content distinct: 20// an empty message has a Content pointer, while a continuation has none. 21type SubmissionRequest struct { 22CatalogRevision string 23CommandArguments json.RawMessage 24Content * string 25Images []ImageAttachment 26Pastes []string 27Type string 28ClientID string 29SubmissionID string 30Name string 31Arguments string 32} 33 34func Submit (ctx context.Context ,conn * Connection ,id string ,payload SubmissionRequest ) (SendResult ,error ) { 35handle ,err := NewSubmission (id ,payload ) 36if err != nil { 37return SendResult {},err 38 } 39return SubmitOperation (ctx ,conn ,handle ) 40} 41 42func SubmitOperation (ctx context.Context ,conn * Connection ,handle * OperationHandle ) (SendResult ,error ) { 43return executeReceipt (ctx ,conn ,handle , []int {http .StatusAccepted },func (body []byte ,status int ) (SendResult ,error ) { 44result ,err := decodeSubmission (body ,status ) 45if err == nil && result .OperationID != handle .ID () { 46return result ,fieldError ("operationId" ) 47 } 48return result ,err 49 },func (ctx context.Context ) (SendResult ,bool ,error ) { 50receipt ,err := ResolveOperation (ctx ,conn ,handle ) 51if err != nil { 52return SendResult {},false ,err 53 } 54result ,err := submissionResult (receipt ) 55return result ,true ,err 56 }) 57} 58 59func InterruptSession (ctx context.Context ,conn * Connection ,id string ) (bool ,error ) { 60session ,err := GetSession (ctx ,conn ,id ) 61if err != nil { 62return false ,err 63 } 64return interruptCaptured (ctx ,conn ,id ,session .wire .Status .RunID ,session .wire .InputOrder ) 65} 66func interruptCaptured (ctx context.Context ,conn * Connection ,id string ,runID * string ,inputOrder int64 ) (bool ,error ) { 67var result protocol.Interruption 68err := executeMutation (ctx ,conn ,operation {Name :"interrupt session" ,BuildRequest :func (base string ,body io.Reader ) (* http.Request ,error ) { 69return protocol .NewInterruptSessionRequestWithBody (base ,id ,"application/json" ,body ) 70 },Body : protocol.InterruptRequest {RunID :runID ,ThroughInputOrder :inputOrder },Policy :noRecovery }, []int {200 },func (data []byte ,_ int )error { 71if err := decodeRequired (data ,& result );err != nil { 72return err 73 } 74switch result .State { 75case "requested" ,"already_ended" : 76return nil 77 } 78return fieldError ("interruption state" ) 79 }) 80return result .State == "requested" ,err 81} 82 83func (c * ChatClient )PrepareTurn (content string ,images []ImageAttachment ,pastes []string ,continuation bool ) (* OperationHandle ,error ) { 84payload := SubmissionRequest {ClientID :c .clientID ,Content :& content ,Type :"user" ,Images :images ,Pastes :pastes } 85if continuation { 86payload .Type ,payload .Content = "continue" ,nil 87 } 88return NewSubmission (c .agentID ,payload ) 89} 90 91func (c * ChatClient )SubmitOperation (ctx context.Context ,handle * OperationHandle ) (* SendResult ,error ) { 92result ,err := SubmitOperation (ctx ,c .conn ,handle ) 93if err != nil { 94return nil ,err 95 } 96return & result ,nil 97} 98 99func (c * ChatClient )ResolveOperation (ctx context.Context ,handle * OperationHandle ) (InputReceipt ,error ) { 100return ResolveOperation (ctx ,c .conn ,handle ) 101} 102 103func (c * ChatClient )Send (ctx context.Context ,content string ,images []ImageAttachment ) (* SendResult ,error ) { 104handle ,err := c .PrepareTurn (content ,images ,nil ,false ) 105if err != nil { 106return nil ,err 107 } 108return c .SubmitOperation (ctx ,handle ) 109} 110 111func (c * ChatClient )CancelSubmission (ctx context.Context ,submissionID string ) (string ,error ) { 112var result protocol.InputCancellation 113err := executeMutation (ctx ,c .conn ,operation {Name :"cancel input" ,BuildRequest :func (base string ,body io.Reader ) (* http.Request ,error ) { 114return protocol .NewCancelInputRequestWithBody (base ,c .agentID ,submissionID ,"application/json" ,body ) 115 },Body :struct {}{},Policy :noRecovery }, []int {200 },func (data []byte ,_ int )error { 116if err := decodeRequired (data ,& result );err != nil { 117return err 118 } 119if result .Input .ID != submissionID { 120return fieldError ("input identity" ) 121 } 122return validInput (result .Input ) 123 }) 124outcome := result .Result 125if outcome == "cancelled" { 126outcome = "cancelled_queued" 127 } 128switch result .Result { 129case "cancelled" ,"interrupt_requested" ,"shared_running" ,"not_pending" : 130default : 131if err == nil { 132err = fieldError ("cancellation result" ) 133 } 134 } 135return outcome ,err 136} 137 138func decodeSubmission (data []byte ,_ int ) (SendResult ,error ) { 139var input protocol.Input 140if err := decodeRequired (data ,& input );err != nil { 141return SendResult {},err 142 } 143if err := validInput (input );err != nil { 144return SendResult {},err 145 } 146return submissionResult (InputReceipt (input )) 147} 148 149func submissionResult (input InputReceipt ) (SendResult ,error ) { 150if err := input .Rejection ();err != nil { 151return SendResult {},err 152 } 153return SendResult {OK :true ,Queued :input .Pending (),OperationID :input .ID ,AcceptanceOrder :input .InputOrder ()},nil 154} 155 156func (c * ChatClient )PrepareCommand (commandID string ,arguments map [string ]json.RawMessage ) (* OperationHandle ,error ) { 157payload ,err := json .Marshal (arguments ) 158if err != nil { 159return nil ,err 160 } 161return NewSubmission (c .agentID ,SubmissionRequest {ClientID :c .clientID ,Type :"command" ,Name :commandID ,CommandArguments :payload }) 162} 163func (c * ChatClient )InterruptObserved (ctx context.Context ,status AgentStatus ) (bool ,error ) { 164return interruptCaptured (ctx ,c .conn ,c .agentID ,status .RunID ,status .InputOrder ) 165}