package daemon import ( "bufio" "context" "encoding/json" "io" "net/http" "albedo/cli/internal/daemon/protocol" ) type ClientBindingCapabilities = protocol.ClientBindingCapabilities type ClientBindingInstall = protocol.ClientBindingInstall type ClientBindingSnapshot = protocol.ClientBindingSnapshot type ClientBindingPending = protocol.ClientBindingPending type ClientMCPServer = protocol.ClientMCPServer type ClientMCPStdio = protocol.ClientMCPStdio type ClientMCPHTTP = protocol.ClientMCPHTTP type ClientBindingReply = protocol.ClientBindingReply type ClientFailed = protocol.ClientFailed type ClientToolDecision = protocol.ClientToolDecision type ClientText = protocol.ClientText type ClientWritten = protocol.ClientWritten // ClientCallbackRequest is the validated projection of the declared wire union. type ClientCallbackRequest struct { Line, Limit *int64 Kind, CallID, Name, Path, Content string Arguments json.RawMessage } type ClientBindingEvent struct { Snapshot *ClientBindingSnapshot Request *ClientBindingPending Generation, Kind, RequestID string Sequence int64 } func DecodeClientRequest(data json.RawMessage) (ClientCallbackRequest, error) { var tag struct { Kind string `json:"kind"` } if err := decodeRequired(data, &tag); err != nil { return ClientCallbackRequest{}, err } result := ClientCallbackRequest{Kind: tag.Kind} switch tag.Kind { case "authorize_tool": var request protocol.ClientAuthorizeTool if err := decodeRequired(data, &request); err != nil { return result, err } if request.CallID == "" || request.Name == "" || !json.Valid(request.Arguments) { return result, fieldError("client tool request") } result.CallID, result.Name, result.Arguments = request.CallID, request.Name, request.Arguments case "read_text": var request protocol.ClientReadText if err := decodeRequired(data, &request); err != nil { return result, err } if request.Path == "" || request.Line != nil && *request.Line < 1 || request.Limit != nil && *request.Limit < 1 { return result, fieldError("client read request") } result.Path, result.Line, result.Limit = request.Path, request.Line, request.Limit case "write_text": var request protocol.ClientWriteText if err := decodeRequired(data, &request); err != nil { return result, err } if request.Path == "" || len(request.Content) > 512<<10 { return result, fieldError("client write request") } result.Path, result.Content = request.Path, request.Content default: return result, fieldError("client request kind") } return result, nil } func validateBinding(snapshot ClientBindingSnapshot) error { if snapshot.Generation == "" || snapshot.BindingID == "" || snapshot.Sequence < 0 || snapshot.Pending == nil || len(snapshot.Pending) > 32 || snapshot.MCPServerNames == nil { return fieldError("client binding") } switch snapshot.State { case "staged", "active", "disconnected": default: return fieldError("client binding state") } ids := map[string]bool{} for _, pending := range snapshot.Pending { if pending.RequestID == "" || pending.Generation != snapshot.Generation || ids[pending.RequestID] { return fieldError("client request identity") } ids[pending.RequestID] = true if _, err := DecodeClientRequest(pending.Request); err != nil { return err } } return nil } func InstallClientBinding(ctx context.Context, conn *Connection, id string, install ClientBindingInstall) (ClientBindingSnapshot, error) { var snapshot ClientBindingSnapshot err := executeMutation(ctx, conn, operation{Name: "install client binding", BuildRequest: func(base string, body io.Reader) (*http.Request, error) { return protocol.NewInstallClientBindingRequestWithBody(base, id, "application/json", body) }, Body: install, Policy: noRecovery}, []int{200}, func(data []byte, _ int) error { if err := decodeRequired(data, &snapshot); err != nil { return err } return validateBinding(snapshot) }) if err == nil && snapshot.BindingID != install.BindingID { err = fieldError("client binding identity") } return snapshot, err } func GetClientBinding(ctx context.Context, conn *Connection, id string) (ClientBindingSnapshot, error) { var snapshot ClientBindingSnapshot err := executeRead(ctx, conn, operation{Name: "read client binding", BuildRequest: func(base string, _ io.Reader) (*http.Request, error) { return protocol.NewGetClientBindingRequest(base, id, nil) }, Policy: readRecovery}, func(data []byte) error { if err := decodeRequired(data, &snapshot); err != nil { return err } return validateBinding(snapshot) }) return snapshot, err } func RenewClientBinding(ctx context.Context, conn *Connection, id, generation string) error { return executeMutation(ctx, conn, operation{Name: "renew client binding", BuildRequest: func(base string, body io.Reader) (*http.Request, error) { return protocol.NewRenewClientBindingRequestWithBody(base, id, "application/merge-patch+json", body) }, Body: protocol.ClientBindingRenew{Generation: generation}, Policy: noRecovery}, []int{200}, func(data []byte, _ int) error { var snapshot ClientBindingSnapshot if err := decodeRequired(data, &snapshot); err != nil { return err } if snapshot.Generation != generation { return fieldError("client binding generation") } return validateBinding(snapshot) }) } func RevokeClientBinding(ctx context.Context, conn *Connection, id, generation string, adoptNative bool) error { return executeMutation(ctx, conn, operation{Name: "revoke client binding", BuildRequest: func(base string, _ io.Reader) (*http.Request, error) { return protocol.NewRevokeClientBindingRequest(base, id, &protocol.RevokeClientBindingParams{Generation: generation, AdoptNative: &adoptNative}) }, Policy: noRecovery}, []int{204}, func(data []byte, _ int) error { if len(data) != 0 { return fieldError("empty binding response") } return nil }) } func ReplyClientBinding(ctx context.Context, conn *Connection, id, requestID, generation string, reply ClientBindingReply) error { return executeMutation(ctx, conn, operation{Name: "reply to client binding", BuildRequest: func(base string, body io.Reader) (*http.Request, error) { return protocol.NewReplyClientBindingRequestWithBody(base, id, requestID, "application/json", body) }, Body: protocol.ClientBindingResponse{Generation: generation, Reply: reply}, Policy: noRecovery}, []int{204}, func(data []byte, _ int) error { if len(data) != 0 { return fieldError("empty binding response") } return nil }) } func WatchClientBinding(ctx context.Context, conn *Connection, id, generation string, consume func(ClientBindingEvent) error) error { return scanEventStream(ctx, conn, operation{Name: "watch client binding", BuildRequest: func(base string, _ io.Reader) (*http.Request, error) { return protocol.NewGetClientBindingRequest(base, id, &protocol.GetClientBindingParams{Generation: &generation}) }, Policy: readRecovery}, streamLimits{requireSSE: true, lineBytes: 1048577, errorBytes: 65536}, func(scanner *bufio.Scanner) error { return scanSSEFrames(ctx, scanner, func(data []byte) error { var tag struct { Kind string `json:"kind"` } if err := decodeRequired(data, &tag); err != nil { return err } event := ClientBindingEvent{Kind: tag.Kind} switch tag.Kind { case "": var snapshot ClientBindingSnapshot if err := decodeRequired(data, &snapshot); err != nil { return err } if err := validateBinding(snapshot); err != nil { return err } event.Kind, event.Generation, event.Sequence, event.Snapshot = "reset", snapshot.Generation, snapshot.Sequence, &snapshot case "request": var wire protocol.ClientBindingRequestEvent if err := decodeRequired(data, &wire); err != nil { return err } if wire.Request.RequestID == "" || wire.Request.Generation != wire.Generation { return fieldError("client event request") } if _, err := DecodeClientRequest(wire.Request.Request); err != nil { return err } event.Generation, event.Sequence, event.Request = wire.Generation, wire.Sequence, &wire.Request case "cancel": var wire protocol.ClientBindingCancelEvent if err := decodeRequired(data, &wire); err != nil { return err } if wire.RequestID == "" { return fieldError("client event request ID") } event.Generation, event.Sequence, event.RequestID = wire.Generation, wire.Sequence, wire.RequestID case "closed": var wire protocol.ClientBindingClosedEvent if err := decodeRequired(data, &wire); err != nil { return err } event.Generation, event.Sequence = wire.Generation, wire.Sequence default: return fieldError("client event kind") } if event.Generation != generation || event.Sequence < 0 { return fieldError("client event generation") } return consume(event) }) }) }