Repository: open-telemetry/opamp-go
Pull request #107 (2022-07-16 13:07:51)

Update client package docs
Partially addresses #100. Also added to some internal funcs which I thought would be helpful to get a quick summary.

codecov[bot]: # [Codecov](https://codecov.io/gh/open-telemetry/opamp-go/pull/107?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) Report
> Merging [#107](https://codecov.io/gh/open-telemetry/opamp-go/pull/107?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) (a0df0fa) into [main](https://codecov.io/gh/open-telemetry/opamp-go/commit/037ef8a44ba0c4d9b04ddbc748c61ad68609afc0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) (037ef8a) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##             main     #107   +/-   ##
=======================================
  Coverage   75.77%   75.77%           
=======================================
  Files          22       22           
  Lines        1577     1577           
=======================================
  Hits         1195     1195           
  Misses        288      288           
  Partials       94       94           
```


| [Impacted Files](https://codecov.io/gh/open-telemetry/opamp-go/pull/107?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | Coverage Δ | |
|---|---|---|
| [client/httpclient.go](https://codecov.io/gh/open-telemetry/opamp-go/pull/107/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2h0dHBjbGllbnQuZ28=) | `94.44% <ø> (ø)` | |
| [client/internal/clientcommon.go](https://codecov.io/gh/open-telemetry/opamp-go/pull/107/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2ludGVybmFsL2NsaWVudGNvbW1vbi5nbw==) | `80.87% <ø> (ø)` | |
| [client/internal/clientstate.go](https://codecov.io/gh/open-telemetry/opamp-go/pull/107/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2ludGVybmFsL2NsaWVudHN0YXRlLmdv) | `90.47% <ø> (ø)` | |
| [client/internal/httpsender.go](https://codecov.io/gh/open-telemetry/opamp-go/pull/107/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2ludGVybmFsL2h0dHBzZW5kZXIuZ28=) | `72.26% <ø> (ø)` | |
| [client/internal/nextmessage.go](https://codecov.io/gh/open-telemetry/opamp-go/pull/107/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2ludGVybmFsL25leHRtZXNzYWdlLmdv) | `100.00% <ø> (ø)` | |
| [client/internal/packagessyncer.go](https://codecov.io/gh/open-telemetry/opamp-go/pull/107/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2ludGVybmFsL3BhY2thZ2Vzc3luY2VyLmdv) | `59.25% <ø> (ø)` | |
| [client/internal/sender.go](https://codecov.io/gh/open-telemetry/opamp-go/pull/107/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2ludGVybmFsL3NlbmRlci5nbw==) | `86.36% <ø> (ø)` | |
| [client/internal/wsreceiver.go](https://codecov.io/gh/open-telemetry/opamp-go/pull/107/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2ludGVybmFsL3dzcmVjZWl2ZXIuZ28=) | `91.42% <ø> (ø)` | |
| [client/internal/wssender.go](https://codecov.io/gh/open-telemetry/opamp-go/pull/107/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L2ludGVybmFsL3dzc2VuZGVyLmdv) | `90.90% <ø> (ø)` | |
| [client/types/callbacks.go](https://codecov.io/gh/open-telemetry/opamp-go/pull/107/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry#diff-Y2xpZW50L3R5cGVzL2NhbGxiYWNrcy5nbw==) | `66.66% <ø> (ø)` | |
| ... and [1 more](https://codecov.io/gh/open-telemetry/opamp-go/pull/107/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/open-telemetry/opamp-go/pull/107?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/open-telemetry/opamp-go/pull/107?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry). Last update [037ef8a...a0df0fa](https://codecov.io/gh/open-telemetry/opamp-go/pull/107?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).


Patch:
diff --git a/README.md b/README.md
index 5474b9c..3d75c91 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,6 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
 Approvers ([@open-telemetry/opamp-go-approvers](https://github.com/orgs/open-telemetry/teams/opamp-go-approvers)):
 
 - [Alex Boten](https://github.com/codeboten), Lightstep
-- [Andy Keller](https://github.com/andykellr), observIQ
 - [Anthony Mirabella](https://github.com/Aneurysm9), AWS
 
 Emeritus Approvers
diff --git a/client/httpclient.go b/client/httpclient.go
index 96f8922..63a7bc1 100644
--- a/client/httpclient.go
+++ b/client/httpclient.go
@@ -20,6 +20,7 @@ type httpClient struct {
 	sender *internal.HTTPSender
 }
 
+// NewHTTP creates a new OpAMP Client that uses HTTP transport.
 func NewHTTP(logger types.Logger) *httpClient {
 	if logger == nil {
 		logger = &sharedinternal.NopLogger{}
@@ -33,6 +34,7 @@ func NewHTTP(logger types.Logger) *httpClient {
 	return w
 }
 
+// Start implements OpAMPClient.Start.
 func (c *httpClient) Start(ctx context.Context, settings types.StartSettings) error {
 	if err := c.common.PrepareStart(ctx, settings); err != nil {
 		return err
@@ -56,30 +58,37 @@ func (c *httpClient) Start(ctx context.Context, settings types.StartSettings) er
 	return nil
 }
 
+// Stop implements OpAMPClient.Stop.
 func (c *httpClient) Stop(ctx context.Context) error {
 	return c.common.Stop(ctx)
 }
 
+// AgentDescription implements OpAMPClient.AgentDescription.
 func (c *httpClient) AgentDescription() *protobufs.AgentDescription {
 	return c.common.AgentDescription()
 }
 
+// SetAgentDescription implements OpAMPClient.SetAgentDescription.
 func (c *httpClient) SetAgentDescription(descr *protobufs.AgentDescription) error {
 	return c.common.SetAgentDescription(descr)
 }
 
+// SetHealth implements OpAMPClient.SetHealth.
 func (c *httpClient) SetHealth(health *protobufs.AgentHealth) error {
 	return c.common.SetHealth(health)
 }
 
+// UpdateEffectiveConfig implements OpAMPClient.UpdateEffectiveConfig.
 func (c *httpClient) UpdateEffectiveConfig(ctx context.Context) error {
 	return c.common.UpdateEffectiveConfig(ctx)
 }
 
+// SetRemoteConfigStatus implements OpAMPClient.SetRemoteConfigStatus.
 func (c *httpClient) SetRemoteConfigStatus(status *protobufs.RemoteConfigStatus) error {
 	return c.common.SetRemoteConfigStatus(status)
 }
 
+// SetPackageStatuses implements OpAMPClient.SetPackageStatuses.
 func (c *httpClient) SetPackageStatuses(statuses *protobufs.PackageStatuses) error {
 	return c.common.SetPackageStatuses(statuses)
 }
@@ -94,5 +103,6 @@ func (c *httpClient) runUntilStopped(ctx context.Context) {
 		c.common.Callbacks,
 		&c.common.ClientSyncedState,
 		c.common.PackagesStateProvider,
+		c.common.Capabilities,
 	)
 }
diff --git a/client/internal/clientcommon.go b/client/internal/clientcommon.go
index 313e232..1a28c65 100644
--- a/client/internal/clientcommon.go
+++ b/client/internal/clientcommon.go
@@ -15,9 +15,13 @@ var (
 	ErrAgentDescriptionMissing      = errors.New("AgentDescription is nil")
 	ErrAgentDescriptionNoAttributes = errors.New("AgentDescription has no attributes defined")
 	ErrAgentHealthMissing           = errors.New("AgentHealth is nil")
+	ErrReportsEffectiveConfigNotSet = errors.New("ReportsEffectiveConfig capability is not set")
+	ErrPackagesStateProviderNotSet  = errors.New("PackagesStateProvider must be set")
+	ErrAcceptsPackagesNotSet        = errors.New("AcceptsPackages and ReportsPackageStatuses must be set")
 
-	errAlreadyStarted       = errors.New("already started")
-	errCannotStopNotStarted = errors.New("cannot stop because not started")
+	errAlreadyStarted               = errors.New("already started")
+	errCannotStopNotStarted         = errors.New("cannot stop because not started")
+	errReportsPackageStatusesNotSet = errors.New("ReportsPackageStatuses capability is not set")
 )
 
 // ClientCommon contains the OpAMP logic that is common between WebSocket and
@@ -26,6 +30,9 @@ type ClientCommon struct {
 	Logger    types.Logger
 	Callbacks types.Callbacks
 
+	// Agent's capabilities defined at Start() time.
+	Capabilities protobufs.AgentCapabilities
+
 	// Client state storage. This is needed if the Server asks to report the state.
 	ClientSyncedState ClientSyncedState
 
@@ -49,12 +56,15 @@ type ClientCommon struct {
 	stoppedSignal chan struct{}
 }
 
+// NewClientCommon creates a new ClientCommon.
 func NewClientCommon(logger types.Logger, sender Sender) ClientCommon {
 	return ClientCommon{
 		Logger: logger, sender: sender, stoppedSignal: make(chan struct{}, 1),
 	}
 }
 
+// PrepareStart prepares the client state for the next Start() call.
+// It returns an error if the client is already started, or if the settings are invalid.
 func (c *ClientCommon) PrepareStart(
 	_ context.Context, settings types.StartSettings,
 ) error {
@@ -62,10 +72,19 @@ func (c *ClientCommon) PrepareStart(
 		return errAlreadyStarted
 	}
 
+	c.Capabilities = settings.Capabilities
+
+	// According to OpAMP spec this capability MUST be set, since all Agents MUST report status.
+	c.Capabilities |= protobufs.AgentCapabilities_ReportsStatus
+
 	if c.ClientSyncedState.AgentDescription() == nil {
 		return ErrAgentDescriptionMissing
 	}
 
+	if c.Capabilities&protobufs.AgentCapabilities_ReportsHealth != 0 && c.ClientSyncedState.Health() == nil {
+		return ErrAgentHealthMissing
+	}
+
 	// Prepare remote config status.
 	if settings.RemoteConfigStatus == nil {
 		// RemoteConfigStatus is not provided. Start with empty.
@@ -82,12 +101,22 @@ func (c *ClientCommon) PrepareStart(
 	c.PackagesStateProvider = settings.PackagesStateProvider
 	var packageStatuses *protobufs.PackageStatuses
 	if settings.PackagesStateProvider != nil {
+		if c.Capabilities&protobufs.AgentCapabilities_AcceptsPackages == 0 ||
+			c.Capabilities&protobufs.AgentCapabilities_ReportsPackageStatuses == 0 {
+			return ErrAcceptsPackagesNotSet
+		}
+
 		// Set package status from the value previously saved in the PackagesStateProvider.
 		var err error
 		packageStatuses, err = settings.PackagesStateProvider.LastReportedStatuses()
 		if err != nil {
 			return err
 		}
+	} else {
+		if c.Capabilities&protobufs.AgentCapabilities_AcceptsPackages != 0 ||
+			c.Capabilities&protobufs.AgentCapabilities_ReportsPackageStatuses != 0 {
+			return ErrPackagesStateProviderNotSet
+		}
 	}
 
 	if packageStatuses == nil {
@@ -112,6 +141,7 @@ func (c *ClientCommon) PrepareStart(
 	return nil
 }
 
+// Stop stops the client. It returns an error if the client is not started.
 func (c *ClientCommon) Stop(ctx context.Context) error {
 	if !c.isStarted {
 		return errCannotStopNotStarted
@@ -133,12 +163,15 @@ func (c *ClientCommon) Stop(ctx context.Context) error {
 	return nil
 }
 
+// IsStopping returns true if Stop() was called.
 func (c *ClientCommon) IsStopping() bool {
 	c.isStoppingMutex.RLock()
 	defer c.isStoppingMutex.RUnlock()
 	return c.isStoppingFlag
 }
 
+// StartConnectAndRun initiates the connection with the Server and starts the
+// background goroutine that handles the communication unitl client is stopped.
 func (c *ClientCommon) StartConnectAndRun(runner func(ctx context.Context)) {
 	// Create a cancellable context.
 	runCtx, runCancel := context.WithCancel(context.Background())
@@ -234,6 +267,10 @@ func (c *ClientCommon) SetHealth(health *protobufs.AgentHealth) error {
 // UpdateEffectiveConfig fetches the current local effective config using
 // GetEffectiveConfig callback and sends it to the Server using provided Sender.
 func (c *ClientCommon) UpdateEffectiveConfig(ctx context.Context) error {
+	if c.Capabilities&protobufs.AgentCapabilities_ReportsEffectiveConfig == 0 {
+		return ErrReportsEffectiveConfigNotSet
+	}
+
 	// Fetch the locally stored config.
 	cfg, err := c.Callbacks.GetEffectiveConfig(ctx)
 	if err != nil {
@@ -256,6 +293,10 @@ func (c *ClientCommon) UpdateEffectiveConfig(ctx context.Context) error {
 	return nil
 }
 
+// SetRemoteConfigStatus sends a status update to the Server if the new RemoteConfigStatus
+// is different from the status we already have in the state.
+// It also remembers the new RemoteConfigStatus in the client state so that it can be
+// sent to the Server when the Server asks for it.
 func (c *ClientCommon) SetRemoteConfigStatus(status *protobufs.RemoteConfigStatus) error {
 	if status.LastRemoteConfigHash == nil {
 		return errLastRemoteConfigHashNil
@@ -283,7 +324,15 @@ func (c *ClientCommon) SetRemoteConfigStatus(status *protobufs.RemoteConfigStatu
 	return nil
 }
 
+// SetPackageStatuses sends a status update to the Server if the new PackageStatuses
+// are different from the ones we already have in the state.
+// It also remembers the new PackageStatuses in the client state so that it can be
+// sent to the Server when the Server asks for it.
 func (c *ClientCommon) SetPackageStatuses(statuses *protobufs.PackageStatuses) error {
+	if c.Capabilities&protobufs.AgentCapabilities_ReportsPackageStatuses == 0 {
+		return errReportsPackageStatusesNotSet
+	}
+
 	if statuses.ServerProvidedAllPackagesHash == nil {
 		return errServerProvidedAllPackagesHashNil
 	}
diff --git a/client/internal/clientstate.go b/client/internal/clientstate.go
index e0f6507..95a04f8 100644
--- a/client/internal/clientstate.go
+++ b/client/internal/clientstate.go
@@ -16,8 +16,8 @@ var (
 )
 
 // ClientSyncedState stores the state of the Agent messages that the OpAMP Client needs to
-// have access to synchronize to the Server. 3 messages can be stored in this store:
-// AgentDescription, RemoteConfigStatus and PackageStatuses.
+// have access to synchronize to the Server. 4 messages can be stored in this store:
+// AgentDescription, AgentHealth, RemoteConfigStatus and PackageStatuses.
 //
 // See OpAMP spec for more details on how state synchronization works:
 // https://github.com/open-telemetry/opamp-spec/blob/main/specification.md#Agent-to-Server-state-synchronization
@@ -82,6 +82,7 @@ func (s *ClientSyncedState) SetAgentDescription(descr *protobufs.AgentDescriptio
 	return nil
 }
 
+// SetHealth sets the AgentHealth in the state.
 func (s *ClientSyncedState) SetHealth(health *protobufs.AgentHealth) error {
 	if health == nil {
 		return ErrAgentHealthMissing
diff --git a/client/internal/httpsender.go b/client/internal/httpsender.go
index 4db1c24..fa5e4fd 100644
--- a/client/internal/httpsender.go
+++ b/client/internal/httpsender.go
@@ -41,6 +41,8 @@ type HTTPSender struct {
 	receiveProcessor receivedProcessor
 }
 
+// NewHTTPSender creates a new Sender that uses HTTP to send messages
+// with default settings.
 func NewHTTPSender(logger types.Logger) *HTTPSender {
 	h := &HTTPSender{
 		SenderCommon:      NewSenderCommon(),
@@ -65,10 +67,11 @@ func (h *HTTPSender) Run(
 	callbacks types.Callbacks,
 	clientSyncedState *ClientSyncedState,
 	packagesStateProvider types.PackagesStateProvider,
+	capabilities protobufs.AgentCapabilities,
 ) {
 	h.url = url
 	h.callbacks = callbacks
-	h.receiveProcessor = newReceivedProcessor(h.logger, callbacks, h, clientSyncedState, packagesStateProvider)
+	h.receiveProcessor = newReceivedProcessor(h.logger, callbacks, h, clientSyncedState, packagesStateProvider, capabilities)
 
 	for {
 		pollingTimer := time.NewTimer(time.Millisecond * time.Duration(atomic.LoadInt64(&h.pollingIntervalMs)))
@@ -102,6 +105,9 @@ func (h *HTTPSender) SetRequestHeader(header http.Header) {
 	h.requestHeader.Set(headerContentType, contentTypeProtobuf)
 }
 
+// makeOneRequestRoundtrip sends a request and receives a response.
+// It will retry the request if the server responds with too many
+// requests or unavailable status.
 func (h *HTTPSender) makeOneRequestRoundtrip(ctx context.Context) {
 	resp, err := h.sendRequestWithRetries(ctx)
 	if err != nil {
diff --git a/client/internal/nextmessage.go b/client/internal/nextmessage.go
index a8da82c..e79c678 100644
--- a/client/internal/nextmessage.go
+++ b/client/internal/nextmessage.go
@@ -18,6 +18,7 @@ type NextMessage struct {
 	messageMutex sync.Mutex
 }
 
+// NewNextMessage returns a new empty NextMessage.
 func NewNextMessage() NextMessage {
 	return NextMessage{
 		nextMessage: &protobufs.AgentToServer{},
diff --git a/client/internal/packagessyncer.go b/client/internal/packagessyncer.go
index 422c217..8f76a2c 100644
--- a/client/internal/packagessyncer.go
+++ b/client/internal/packagessyncer.go
@@ -23,6 +23,7 @@ type packagesSyncer struct {
 	doneCh   chan struct{}
 }
 
+// NewPackagesSyncer creates a new packages syncer.
 func NewPackagesSyncer(
 	logger types.Logger,
 	available *protobufs.PackagesAvailable,
@@ -40,6 +41,7 @@ func NewPackagesSyncer(
 	}
 }
 
+// Sync performs the package syncing process.
 func (s *packagesSyncer) Sync(ctx context.Context) error {
 
 	defer func() {
@@ -95,6 +97,7 @@ func (s *packagesSyncer) initStatuses() error {
 	return nil
 }
 
+// doSync performs the actual syncing process.
 func (s *packagesSyncer) doSync(ctx context.Context) {
 	hash, err := s.localState.AllPackagesHash()
 	if err != nil {
@@ -136,6 +139,7 @@ func (s *packagesSyncer) doSync(ctx context.Context) {
 	_ = s.reportStatuses(true)
 }
 
+// syncPackage downloads the package from the server and installs it.
 func (s *packagesSyncer) syncPackage(
 	ctx context.Context,
 	pkgName string,
@@ -214,6 +218,9 @@ func (s *packagesSyncer) syncPackage(
 	return err
 }
 
+// syncPackageFile downloads the package file from the server.
+// If the file already exists and contents are
+// unchanged, it is not downloaded again.
 func (s *packagesSyncer) syncPackageFile(
 	ctx context.Context, pkgName string, file *protobufs.DownloadableFile,
 ) error {
@@ -225,6 +232,7 @@ func (s *packagesSyncer) syncPackageFile(
 	return err
 }
 
+// shouldDownloadFile returns true if the file should be downloaded.
 func (s *packagesSyncer) shouldDownloadFile(
 	packageName string,
 	file *protobufs.DownloadableFile,
@@ -246,6 +254,7 @@ func (s *packagesSyncer) shouldDownloadFile(
 	return false, nil
 }
 
+// downloadFile downloads the file from the server.
 func (s *packagesSyncer) downloadFile(ctx context.Context, pkgName string, file *protobufs.DownloadableFile) error {
 	s.logger.Debugf("Downloading package %s file from %s", pkgName, file.DownloadUrl)
 
@@ -274,6 +283,9 @@ func (s *packagesSyncer) downloadFile(ctx context.Context, pkgName string, file
 	return nil
 }
 
+// deleteUnneededLocalPackages deletes local packages that are not
+// needed anymore. This is done by comparing the local package state
+// with the server's package state.
 func (s *packagesSyncer) deleteUnneededLocalPackages() error {
 	// Read the list of packages we have locally.
 	localPackages, err := s.localState.Packages()
@@ -303,6 +315,9 @@ func (s *packagesSyncer) deleteUnneededLocalPackages() error {
 	return lastErr
 }
 
+// reportStatuses saves the last reported statuses to provider and client state.
+// If sendImmediately is true, the statuses are scheduled to be
+// sent to the server.
 func (s *packagesSyncer) reportStatuses(sendImmediately bool) error {
 	// Save it in the user-supplied state provider.
 	if err := s.localState.SetLastReportedStatuses(s.statuses); err != nil {
diff --git a/client/internal/receivedprocessor.go b/client/internal/receivedprocessor.go
index 15823d4..f8d6184 100644
--- a/client/internal/receivedprocessor.go
+++ b/client/internal/receivedprocessor.go
@@ -23,6 +23,9 @@ type receivedProcessor struct {
 	clientSyncedState *ClientSyncedState
 
 	packagesStateProvider types.PackagesStateProvider
+
+	// Agent's capabilities defined at Start() time.
+	capabilities protobufs.AgentCapabilities
 }
 
 func newReceivedProcessor(
@@ -31,6 +34,7 @@ func newReceivedProcessor(
 	sender Sender,
 	clientSyncedState *ClientSyncedState,
 	packagesStateProvider types.PackagesStateProvider,
+	capabilities protobufs.AgentCapabilities,
 ) receivedProcessor {
 	return receivedProcessor{
 		logger:                logger,
@@ -38,6 +42,7 @@ func newReceivedProcessor(
 		sender:                sender,
 		clientSyncedState:     clientSyncedState,
 		packagesStateProvider: packagesStateProvider,
+		capabilities:          capabilities,
 	}
 }
 
@@ -57,26 +62,63 @@ func (r *receivedProcessor) ProcessReceivedMessage(ctx context.Context, msg *pro
 			r.logger.Errorf("cannot processed received flags:%v", err)
 		}
 
-		msgData := &types.MessageData{
-			RemoteConfig: msg.RemoteConfig,
+		msgData := &types.MessageData{}
+
+		if msg.RemoteConfig != nil {
+			if r.hasCapability(protobufs.AgentCapabilities_AcceptsRemoteConfig) {
+				msgData.RemoteConfig = msg.RemoteConfig
+			} else {
+				r.logger.Debugf("Ignoring RemoteConfig, agent does not have AcceptsRemoteConfig capability")
+			}
 		}
 
 		if msg.ConnectionSettings != nil {
-			msgData.OwnMetricsConnSettings = msg.ConnectionSettings.OwnMetrics
-			msgData.OwnTracesConnSettings = msg.ConnectionSettings.OwnTraces
-			msgData.OwnLogsConnSettings = msg.ConnectionSettings.OwnLogs
-			msgData.OtherConnSettings = msg.ConnectionSettings.OtherConnections
+			if msg.ConnectionSettings.OwnMetrics != nil {
+				if r.hasCapability(protobufs.AgentCapabilities_ReportsOwnMetrics) {
+					msgData.OwnMetricsConnSettings = msg.ConnectionSettings.OwnMetrics
+				} else {
+					r.logger.Debugf("Ignoring OwnMetrics, agent does not have ReportsOwnMetrics capability")
+				}
+			}
+
+			if msg.ConnectionSettings.OwnTraces != nil {
+				if r.hasCapability(protobufs.AgentCapabilities_ReportsOwnTraces) {
+					msgData.OwnTracesConnSettings = msg.ConnectionSettings.OwnTraces
+				} else {
+					r.logger.Debugf("Ignoring OwnTraces, agent does not have ReportsOwnTraces capability")
+				}
+			}
+
+			if msg.ConnectionSettings.OwnLogs != nil {
+				if r.hasCapability(protobufs.AgentCapabilities_ReportsOwnLogs) {
+					msgData.OwnLogsConnSettings = msg.ConnectionSettings.OwnLogs
+				} else {
+					r.logger.Debugf("Ignoring OwnLogs, agent does not have ReportsOwnLogs capability")
+				}
+			}
+
+			if msg.ConnectionSettings.OtherConnections != nil {
+				if r.hasCapability(protobufs.AgentCapabilities_AcceptsOtherConnectionSettings) {
+					msgData.OtherConnSettings = msg.ConnectionSettings.OtherConnections
+				} else {
+					r.logger.Debugf("Ignoring OtherConnections, agent does not have AcceptsOtherConnectionSettings capability")
+				}
+			}
 		}
 
 		if msg.PackagesAvailable != nil {
-			msgData.PackagesAvailable = msg.PackagesAvailable
-			msgData.PackageSyncer = NewPackagesSyncer(
-				r.logger,
-				msgData.PackagesAvailable,
-				r.sender,
-				r.clientSyncedState,
-				r.packagesStateProvider,
-			)
+			if r.hasCapability(protobufs.AgentCapabilities_AcceptsPackages) {
+				msgData.PackagesAvailable = msg.PackagesAvailable
+				msgData.PackageSyncer = NewPackagesSyncer(
+					r.logger,
+					msgData.PackagesAvailable,
+					r.sender,
+					r.clientSyncedState,
+					r.packagesStateProvider,
+				)
+			} else {
+				r.logger.Debugf("Ignoring PackagesAvailable, agent does not have AcceptsPackages capability")
+			}
 		}
 
 		if msg.AgentIdentification != nil {
@@ -101,6 +143,10 @@ func (r *receivedProcessor) ProcessReceivedMessage(ctx context.Context, msg *pro
 	}
 }
 
+func (r *receivedProcessor) hasCapability(capability protobufs.AgentCapabilities) bool {
+	return r.capabilities&capability != 0
+}
+
 func (r *receivedProcessor) rcvFlags(
 	ctx context.Context,
 	flags protobufs.ServerToAgent_Flags,
@@ -139,10 +185,14 @@ func (r *receivedProcessor) rcvOpampConnectionSettings(ctx context.Context, sett
 		return
 	}
 
-	err := r.callbacks.OnOpampConnectionSettings(ctx, settings.Opamp)
-	if err == nil {
-		// TODO: verify connection using new settings.
-		r.callbacks.OnOpampConnectionSettingsAccepted(settings.Opamp)
+	if r.hasCapability(protobufs.AgentCapabilities_AcceptsOpAMPConnectionSettings) {
+		err := r.callbacks.OnOpampConnectionSettings(ctx, settings.Opamp)
+		if err == nil {
+			// TODO: verify connection using new settings.
+			r.callbacks.OnOpampConnectionSettingsAccepted(settings.Opamp)
+		}
+	} else {
+		r.logger.Debugf("Ignoring Opamp, agent does not have AcceptsOpAMPConnectionSettings capability")
 	}
 }
 
diff --git a/client/internal/sender.go b/client/internal/sender.go
index fa92856..28a283d 100644
--- a/client/internal/sender.go
+++ b/client/internal/sender.go
@@ -23,7 +23,7 @@ type Sender interface {
 	SetInstanceUid(instanceUid string) error
 }
 
-// SenderCommon is partial Sender implementation that is common WebSocket and plain
+// SenderCommon is partial Sender implementation that is common between WebSocket and plain
 // HTTP transports. This struct is intended to be embedded in the WebSocket and
 // HTTP Sender implementations.
 type SenderCommon struct {
@@ -34,6 +34,8 @@ type SenderCommon struct {
 	nextMessage NextMessage
 }
 
+// NewSenderCommon creates a new SenderCommon. This is intended to be used by
+// the WebSocket and HTTP Sender implementations.
 func NewSenderCommon() SenderCommon {
 	return SenderCommon{
 		hasPendingMessage: make(chan struct{}, 1),
diff --git a/client/internal/wsreceiver.go b/client/internal/wsreceiver.go
index 08bed68..75461a2 100644
--- a/client/internal/wsreceiver.go
+++ b/client/internal/wsreceiver.go
@@ -20,6 +20,8 @@ type wsReceiver struct {
 	processor receivedProcessor
 }
 
+// NewWSReceiver creates a new Receiver that uses WebSocket to receive
+// messages from the server.
 func NewWSReceiver(
 	logger types.Logger,
 	callbacks types.Callbacks,
@@ -27,18 +29,20 @@ func NewWSReceiver(
 	sender *WSSender,
 	clientSyncedState *ClientSyncedState,
 	packagesStateProvider types.PackagesStateProvider,
+	capabilities protobufs.AgentCapabilities,
 ) *wsReceiver {
 	w := &wsReceiver{
 		conn:      conn,
 		logger:    logger,
 		sender:    sender,
 		callbacks: callbacks,
-		processor: newReceivedProcessor(logger, callbacks, sender, clientSyncedState, packagesStateProvider),
+		processor: newReceivedProcessor(logger, callbacks, sender, clientSyncedState, packagesStateProvider, capabilities),
 	}
 
 	return w
 }
 
+// ReceiverLoop runs the receiver loop. To stop the receiver cancel the context.
 func (r *wsReceiver) ReceiverLoop(ctx context.Context) {
 	runContext, cancelFunc := context.WithCancel(ctx)
 
diff --git a/client/internal/wssender.go b/client/internal/wssender.go
index 0ea8a49..9a7b646 100644
--- a/client/internal/wssender.go
+++ b/client/internal/wssender.go
@@ -19,6 +19,8 @@ type WSSender struct {
 	stopped chan struct{}
 }
 
+// NewSender creates a new Sender that uses WebSocket to send
+// messages to the server.
 func NewSender(logger types.Logger) *WSSender {
 	return &WSSender{
 		logger:       logger,
diff --git a/client/types/callbacks.go b/client/types/callbacks.go
index b08c388..57799f9 100644
--- a/client/types/callbacks.go
+++ b/client/types/callbacks.go
@@ -39,6 +39,10 @@ type MessageData struct {
 }
 
 type Callbacks interface {
+	// OnConnect is called when the connection is successfully established to the Server.
+	// May be called after Start() is called and every time a connection is established to the Server.
+	// For WebSocket clients this is called after the handshake is completed without any error.
+	// For HTTP clients this is called for any request if the response status is OK.
 	OnConnect()
 
 	// OnConnectFailed is called when the connection to the Server cannot be established.
@@ -63,7 +67,7 @@ type Callbacks interface {
 	OnMessage(ctx context.Context, msg *MessageData)
 
 	// OnOpampConnectionSettings is called when the Agent receives an OpAMP
-	// connection settings offer from the Server. Typically the settings can specify
+	// connection settings offer from the Server. Typically, the settings can specify
 	// authorization headers or TLS certificate, potentially also a different
 	// OpAMP destination to work with.
 	//
diff --git a/client/types/startsettings.go b/client/types/startsettings.go
index 83f82c9..8a2e4c2 100644
--- a/client/types/startsettings.go
+++ b/client/types/startsettings.go
@@ -38,4 +38,8 @@ type StartSettings struct {
 	// If nil then ReportsPackageStatuses and AcceptsPackages capabilities will be disabled,
 	// i.e. package status reporting and syncing from the Server will be disabled.
 	PackagesStateProvider PackagesStateProvider
+
+	// Defines the capabilities of the Agent. AgentCapabilities_ReportsStatus bit does not need to
+	// be set in this field, it will be set automatically since it is required by OpAMP protocol.
+	Capabilities protobufs.AgentCapabilities
 }
diff --git a/client/wsclient.go b/client/wsclient.go
index 551a13e..468ec0f 100644
--- a/client/wsclient.go
+++ b/client/wsclient.go
@@ -37,6 +37,7 @@ type wsClient struct {
 	sender *internal.WSSender
 }
 
+// NewWebSocket creates a new OpAMP Client that uses WebSocket transport.
 func NewWebSocket(logger types.Logger) *wsClient {
 	if logger == nil {
 		logger = &sharedinternal.NopLogger{}
@@ -235,6 +236,7 @@ func (c *wsClient) runOneCycle(ctx context.Context) {
 		c.sender,
 		&c.common.ClientSyncedState,
 		c.common.PackagesStateProvider,
+		c.common.Capabilities,
 	)
 	r.ReceiverLoop(ctx)
 
diff --git a/internal/examples/agent/agent/agent.go b/internal/examples/agent/agent/agent.go
index a8f79a5..fbfc274 100644
--- a/internal/examples/agent/agent/agent.go
+++ b/internal/examples/agent/agent/agent.go
@@ -103,6 +103,9 @@ func (agent *Agent) start() error {
 			OnMessageFunc: agent.onMessage,
 		},
 		RemoteConfigStatus: agent.remoteConfigStatus,
+		Capabilities: protobufs.AgentCapabilities_AcceptsRemoteConfig |
+			protobufs.AgentCapabilities_ReportsEffectiveConfig |
+			protobufs.AgentCapabilities_ReportsOwnMetrics,
 	}
 	err := agent.opampClient.SetAgentDescription(agent.agentDescription)
 	if err != nil {
diff --git a/internal/examples/supervisor/supervisor/supervisor.go b/internal/examples/supervisor/supervisor/supervisor.go
index 8eecce7..161e1a4 100644
--- a/internal/examples/supervisor/supervisor/supervisor.go
+++ b/internal/examples/supervisor/supervisor/supervisor.go
@@ -151,12 +151,21 @@ func (s *Supervisor) startOpAMP() error {
 			},
 			OnMessageFunc: s.onMessage,
 		},
+		Capabilities: protobufs.AgentCapabilities_AcceptsRemoteConfig |
+			protobufs.AgentCapabilities_ReportsEffectiveConfig |
+			protobufs.AgentCapabilities_ReportsOwnMetrics |
+			protobufs.AgentCapabilities_ReportsHealth,
 	}
 	err := s.opampClient.SetAgentDescription(s.createAgentDescription())
 	if err != nil {
 		return err
 	}
 
+	err = s.opampClient.SetHealth(&protobufs.AgentHealth{Up: false})
+	if err != nil {
+		return err
+	}
+
 	s.logger.Debugf("Starting OpAMP client...")
 
 	err = s.opampClient.Start(context.Background(), settings)
diff --git a/internal/proto/opamp.proto b/internal/proto/opamp.proto
index 5536d8b..851a7e7 100644
--- a/internal/proto/opamp.proto
+++ b/internal/proto/opamp.proto
@@ -538,6 +538,8 @@ enum AgentCapabilities {
     AcceptsOtherConnectionSettings = 0x00000200;
     // The Agent can accept restart requests.
     AcceptsRestartCommand          = 0x00000400;
+    // The Agent will report Health via AgentToServer.health field.
+    ReportsHealth                  = 0x00000800;
 
     // Add new capabilities here, continuing with the least significant unused bit.
 }
diff --git a/protobufs/opamp.pb.go b/protobufs/opamp.pb.go
index 4fe0c90..ca75af2 100644
--- a/protobufs/opamp.pb.go
+++ b/protobufs/opamp.pb.go
@@ -138,6 +138,8 @@ const (
 	AgentCapabilities_AcceptsOtherConnectionSettings AgentCapabilities = 512
 	// The Agent can accept restart requests.
 	AgentCapabilities_AcceptsRestartCommand AgentCapabilities = 1024
+	// The Agent will report Health via AgentToServer.health field.
+	AgentCapabilities_ReportsHealth AgentCapabilities = 2048
 )
 
 // Enum value maps for AgentCapabilities.
@@ -155,6 +157,7 @@ var (
 		256:  "AcceptsOpAMPConnectionSettings",
 		512:  "AcceptsOtherConnectionSettings",
 		1024: "AcceptsRestartCommand",
+		2048: "ReportsHealth",
 	}
 	AgentCapabilities_value = map[string]int32{
 		"UnspecifiedAgentCapability":     0,
@@ -169,6 +172,7 @@ var (
 		"AcceptsOpAMPConnectionSettings": 256,
 		"AcceptsOtherConnectionSettings": 512,
 		"AcceptsRestartCommand":          1024,
+		"ReportsHealth":                  2048,
 	}
 )
 
@@ -2953,7 +2957,7 @@ var file_opamp_proto_rawDesc = []byte{
 	0x63, 0x63, 0x65, 0x70, 0x74, 0x73, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x53, 0x74,
 	0x61, 0x74, 0x75, 0x73, 0x10, 0x10, 0x12, 0x1c, 0x0a, 0x18, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x73,
 	0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
-	0x67, 0x73, 0x10, 0x20, 0x2a, 0xd4, 0x02, 0x0a, 0x11, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61,
+	0x67, 0x73, 0x10, 0x20, 0x2a, 0xe8, 0x02, 0x0a, 0x11, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61,
 	0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x6e,
 	0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x61,
 	0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x65,
@@ -2974,11 +2978,12 @@ var file_opamp_proto_rawDesc = []byte{
 	0x63, 0x65, 0x70, 0x74, 0x73, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
 	0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x10, 0x80, 0x04, 0x12,
 	0x1a, 0x0a, 0x15, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x73, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72,
-	0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x10, 0x80, 0x08, 0x42, 0x2e, 0x5a, 0x2c, 0x67,
-	0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x74,
-	0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2f, 0x6f, 0x70, 0x61, 0x6d, 0x70, 0x2d, 0x67,
-	0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x33,
+	0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x10, 0x80, 0x08, 0x12, 0x12, 0x0a, 0x0d, 0x52,
+	0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x10, 0x80, 0x10, 0x42,
+	0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70,
+	0x65, 0x6e, 0x2d, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2f, 0x6f, 0x70, 0x61,
+	0x6d, 0x70, 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x73, 0x62,
+	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (


Test patch:
diff --git a/client/clientimpl_test.go b/client/clientimpl_test.go
index 5beafea..226b0e6 100644
--- a/client/clientimpl_test.go
+++ b/client/clientimpl_test.go
@@ -332,6 +332,7 @@ func TestFirstStatusReport(t *testing.T) {
 					atomic.AddInt64(&remoteConfigReceived, 1)
 				},
 			},
+			Capabilities: protobufs.AgentCapabilities_AcceptsRemoteConfig,
 		}
 		settings.OpAMPServerURL = "ws://" + srv.Endpoint
 		startClient(t, settings, client)
@@ -430,6 +431,7 @@ func TestSetEffectiveConfig(t *testing.T) {
 					return sendConfig, nil
 				},
 			},
+			Capabilities: protobufs.AgentCapabilities_ReportsEffectiveConfig,
 		}
 		settings.OpAMPServerURL = "ws://" + srv.Endpoint
 		prepareClient(t, &settings, client)
@@ -655,6 +657,11 @@ func TestConnectionSettings(t *testing.T) {
 					return nil
 				},
 			},
+			Capabilities: protobufs.AgentCapabilities_ReportsOwnTraces |
+				protobufs.AgentCapabilities_ReportsOwnMetrics |
+				protobufs.AgentCapabilities_ReportsOwnLogs |
+				protobufs.AgentCapabilities_AcceptsOtherConnectionSettings |
+				protobufs.AgentCapabilities_AcceptsOpAMPConnectionSettings,
 		}
 		settings.OpAMPServerURL = "ws://" + srv.Endpoint
 		prepareClient(t, &settings, client)
@@ -685,6 +692,7 @@ func TestReportAgentDescription(t *testing.T) {
 		// Start a client.
 		settings := types.StartSettings{
 			OpAMPServerURL: "ws://" + srv.Endpoint,
+			Capabilities:   protobufs.AgentCapabilities_ReportsEffectiveConfig,
 		}
 		prepareClient(t, &settings, client)
 
@@ -747,6 +755,7 @@ func TestReportAgentHealth(t *testing.T) {
 		// Start a client.
 		settings := types.StartSettings{
 			OpAMPServerURL: "ws://" + srv.Endpoint,
+			Capabilities:   protobufs.AgentCapabilities_ReportsEffectiveConfig | protobufs.AgentCapabilities_ReportsHealth,
 		}
 		prepareClient(t, &settings, client)
 
@@ -906,6 +915,7 @@ func verifyRemoteConfigUpdate(t *testing.T, successCase bool, expectStatus *prot
 					}
 				},
 			},
+			Capabilities: protobufs.AgentCapabilities_AcceptsRemoteConfig,
 		}
 		prepareClient(t, &settings, client)
 
@@ -1056,6 +1066,7 @@ func verifyUpdatePackages(t *testing.T, testCase packageTestCase) {
 				OnMessageFunc: onMessageFunc,
 			},
 			PackagesStateProvider: localPackageState,
+			Capabilities:          protobufs.AgentCapabilities_AcceptsPackages | protobufs.AgentCapabilities_ReportsPackageStatuses,
 		}
 		prepareClient(t, &settings, client)
 
@@ -1257,3 +1268,112 @@ func TestUpdatePackages(t *testing.T) {
 		})
 	}
 }
+
+func TestMissingCapabilities(t *testing.T) {
+	testClients(t, func(t *testing.T, client OpAMPClient) {
+		// Start a server.
+		srv := internal.StartMockServer(t)
+		srv.EnableExpectMode()
+
+		// Start a client.
+		settings := types.StartSettings{
+			Callbacks: types.CallbacksStruct{
+				OnMessageFunc: func(ctx context.Context, msg *types.MessageData) {
+					// These fields must not be set since we did not define the capabilities to accept them.
+					assert.Nil(t, msg.RemoteConfig)
+					assert.Nil(t, msg.OwnLogsConnSettings)
+					assert.Nil(t, msg.OwnMetricsConnSettings)
+					assert.Nil(t, msg.OwnTracesConnSettings)
+					assert.Nil(t, msg.OtherConnSettings)
+					assert.Nil(t, msg.PackagesAvailable)
+				},
+				OnOpampConnectionSettingsFunc: func(
+					ctx context.Context, settings *protobufs.OpAMPConnectionSettings,
+				) error {
+					assert.Fail(t, "should not be called since capability is not set to accept it")
+					return nil
+				},
+			},
+		}
+		settings.OpAMPServerURL = "ws://" + srv.Endpoint
+		prepareClient(t, &settings, client)
+
+		require.NoError(t, client.Start(context.Background(), settings))
+
+		// Change the config.
+		err := client.UpdateEffectiveConfig(context.Background())
+
+		assert.ErrorIs(t, err, internal.ErrReportsEffectiveConfigNotSet)
+
+		remoteCfg := createRemoteConfig()
+		// ---> Server
+		srv.Expect(func(msg *protobufs.AgentToServer) *protobufs.ServerToAgent {
+			opampSettings := &protobufs.OpAMPConnectionSettings{DestinationEndpoint: "http://opamp.com"}
+			metricsSettings := &protobufs.TelemetryConnectionSettings{DestinationEndpoint: "http://metrics.com"}
+			tracesSettings := &protobufs.TelemetryConnectionSettings{DestinationEndpoint: "http://traces.com"}
+			logsSettings := &protobufs.TelemetryConnectionSettings{DestinationEndpoint: "http://logs.com"}
+			otherSettings := &protobufs.OtherConnectionSettings{DestinationEndpoint: "http://other.com"}
+			hash := []byte{1, 2, 3}
+
+			return &protobufs.ServerToAgent{
+				InstanceUid:  msg.InstanceUid,
+				RemoteConfig: remoteCfg,
+				ConnectionSettings: &protobufs.ConnectionSettingsOffers{
+					Hash:       hash,
+					Opamp:      opampSettings,
+					OwnMetrics: metricsSettings,
+					OwnTraces:  tracesSettings,
+					OwnLogs:    logsSettings,
+					OtherConnections: map[string]*protobufs.OtherConnectionSettings{
+						"other": otherSettings,
+					},
+				},
+				PackagesAvailable: &protobufs.PackagesAvailable{
+					Packages:        map[string]*protobufs.PackageAvailable{},
+					AllPackagesHash: []byte{1, 2, 3, 4, 5},
+				},
+			}
+		})
+
+		// Shutdown the Server.
+		srv.Close()
+
+		// Shutdown the client.
+		err = client.Stop(context.Background())
+		assert.NoError(t, err)
+	})
+}
+
+func TestMissingPackagesStateProvider(t *testing.T) {
+	testClients(t, func(t *testing.T, client OpAMPClient) {
+		// Start a client.
+		settings := types.StartSettings{
+			Callbacks:    types.CallbacksStruct{},
+			Capabilities: protobufs.AgentCapabilities_AcceptsPackages | protobufs.AgentCapabilities_ReportsPackageStatuses,
+		}
+		prepareClient(t, &settings, client)
+
+		assert.ErrorIs(t, client.Start(context.Background(), settings), internal.ErrPackagesStateProviderNotSet)
+
+		// Start a client.
+		localPackageState := internal.NewInMemPackagesStore()
+		settings = types.StartSettings{
+			Callbacks:             types.CallbacksStruct{},
+			PackagesStateProvider: localPackageState,
+			Capabilities:          protobufs.AgentCapabilities_AcceptsPackages,
+		}
+		prepareClient(t, &settings, client)
+
+		assert.ErrorIs(t, client.Start(context.Background(), settings), internal.ErrAcceptsPackagesNotSet)
+
+		// Start a client.
+		settings = types.StartSettings{
+			Callbacks:             types.CallbacksStruct{},
+			PackagesStateProvider: localPackageState,
+			Capabilities:          protobufs.AgentCapabilities_ReportsPackageStatuses,
+		}
+		prepareClient(t, &settings, client)
+
+		assert.ErrorIs(t, client.Start(context.Background(), settings), internal.ErrAcceptsPackagesNotSet)
+	})
+}
diff --git a/client/internal/wsreceiver_test.go b/client/internal/wsreceiver_test.go
index c767469..4319aad 100644
--- a/client/internal/wsreceiver_test.go
+++ b/client/internal/wsreceiver_test.go
@@ -74,7 +74,7 @@ func TestServerToAgentCommand(t *testing.T) {
 				remoteConfigStatus: &protobufs.RemoteConfigStatus{},
 			}
 			sender := WSSender{}
-			receiver := NewWSReceiver(TestLogger{t}, callbacks, nil, &sender, &clientSyncedState, nil)
+			receiver := NewWSReceiver(TestLogger{t}, callbacks, nil, &sender, &clientSyncedState, nil, 0)
 			receiver.processor.ProcessReceivedMessage(context.Background(), &protobufs.ServerToAgent{
 				Command: test.command,
 			})
@@ -97,7 +97,7 @@ func TestServerToAgentCommandExclusive(t *testing.T) {
 		},
 	}
 	clientSyncedState := ClientSyncedState{}
-	receiver := NewWSReceiver(TestLogger{t}, callbacks, nil, nil, &clientSyncedState, nil)
+	receiver := NewWSReceiver(TestLogger{t}, callbacks, nil, nil, &clientSyncedState, nil, 0)
 	receiver.processor.ProcessReceivedMessage(context.Background(), &protobufs.ServerToAgent{
 		Command: &protobufs.ServerToAgentCommand{
 			Type: protobufs.ServerToAgentCommand_Restart,