diff --git a/pkg/backends/backend.go b/pkg/backends/backend.go index 2fa85be..2565360 100644 --- a/pkg/backends/backend.go +++ b/pkg/backends/backend.go @@ -79,14 +79,8 @@ func (o *Options) UnmarshalJSON(data []byte) error { } func (o *Options) MarshalJSON() ([]byte, error) { - type Alias Options - aux := &struct { - *Alias - }{ - Alias: (*Alias)(o), - } - // Get backend and marshal it + var backendOptions map[string]any backend := o.getBackend() if backend != nil { optionsData, err := json.Marshal(backend) @@ -94,13 +88,19 @@ func (o *Options) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("failed to marshal backend options: %w", err) } // Create a new map to avoid concurrent map writes - aux.BackendOptions = make(map[string]any) - if err := json.Unmarshal(optionsData, &aux.BackendOptions); err != nil { + backendOptions = make(map[string]any) + if err := json.Unmarshal(optionsData, &backendOptions); err != nil { return nil, fmt.Errorf("failed to unmarshal backend options to map: %w", err) } } - return json.Marshal(aux) + return json.Marshal(&struct { + BackendType BackendType `json:"backend_type"` + BackendOptions map[string]any `json:"backend_options,omitempty"` + }{ + BackendType: o.BackendType, + BackendOptions: backendOptions, + }) } // setBackendOptions stores the backend in the appropriate typed field diff --git a/pkg/instance/options.go b/pkg/instance/options.go index 59cb31f..57a3ce9 100644 --- a/pkg/instance/options.go +++ b/pkg/instance/options.go @@ -7,6 +7,7 @@ import ( "llamactl/pkg/config" "llamactl/pkg/validation" "log" + "maps" "slices" "sync" ) @@ -144,15 +145,25 @@ func (c *Options) UnmarshalJSON(data []byte) error { // MarshalJSON implements custom JSON marshaling for Options func (c *Options) MarshalJSON() ([]byte, error) { - // Use anonymous struct to avoid recursion type Alias Options - aux := struct { + + // Make a copy of the struct + temp := *c + + // Copy environment map to avoid concurrent access issues + if temp.Environment != nil { + envCopy := make(map[string]string, len(temp.Environment)) + maps.Copy(envCopy, temp.Environment) + temp.Environment = envCopy + } + + aux := &struct { Nodes []string `json:"nodes,omitempty"` // Output as JSON array BackendType backends.BackendType `json:"backend_type"` BackendOptions map[string]any `json:"backend_options,omitempty"` *Alias }{ - Alias: (*Alias)(c), + Alias: (*Alias)(&temp), } // Convert nodes map to array (sorted for consistency) @@ -169,13 +180,12 @@ func (c *Options) MarshalJSON() ([]byte, error) { aux.BackendType = c.BackendOptions.BackendType // Marshal the backends.Options struct to get the properly formatted backend options - // Marshal a pointer to trigger the pointer receiver MarshalJSON method backendData, err := json.Marshal(&c.BackendOptions) if err != nil { return nil, fmt.Errorf("failed to marshal backend options: %w", err) } - // Unmarshal into a temporary struct to extract the backend_options map + // Unmarshal into a new temporary map to extract the backend_options var tempBackend struct { BackendOptions map[string]any `json:"backend_options,omitempty"` }