Skip to content
This repository was archived by the owner on Sep 11, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ NOTE: all releases may include dependency updates, not specifically mentioned

- feat: integrate try-as library [#912](https://github.com/hypermodeinc/modus/pull/912)
- fix: check topic actor status before publishing events [#918](https://github.com/hypermodeinc/modus/pull/918)
- feat: update health endpoint [#924](https://github.com/hypermodeinc/modus/pull/924)

## 2025-06-25 - Runtime v0.18.2

Expand Down
24 changes: 0 additions & 24 deletions runtime/actors/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,27 +323,3 @@ func ListActiveAgents(ctx context.Context) ([]AgentInfo, error) {

return results, nil
}

func ListLocalAgents(ctx context.Context) []AgentInfo {
if _actorSystem == nil {
return nil
}

span, _ := utils.NewSentrySpanForCurrentFunc(ctx)
defer span.Finish()

actors := _actorSystem.Actors()
results := make([]AgentInfo, 0, len(actors))

for _, pid := range actors {
if actor, ok := pid.Actor().(*wasmAgentActor); ok {
results = append(results, AgentInfo{
Id: actor.agentId,
Name: actor.agentName,
Status: actor.status,
})
}
}

return results
}
6 changes: 5 additions & 1 deletion runtime/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,15 @@ func ModusHomeDir() string {
}

func KubernetesNamespace() (string, bool) {
return kubernetesNamespace()
}

var kubernetesNamespace = sync.OnceValues(func() (string, bool) {
if ns := os.Getenv("NAMESPACE"); ns != "" {
return ns, true
}
if data, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
return strings.TrimSpace(string(data)), true
}
return "", false
}
})
49 changes: 20 additions & 29 deletions runtime/httpserver/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,34 @@
package httpserver

import (
"encoding/json"
"fmt"
"net/http"
"runtime"

"github.com/hypermodeinc/modus/runtime/actors"
"github.com/hypermodeinc/modus/runtime/app"
"github.com/hypermodeinc/modus/runtime/logger"
"github.com/hypermodeinc/modus/runtime/utils"
)

var healthHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
env := app.Config().Environment()
ver := app.VersionNumber()
agents := actors.ListLocalAgents(r.Context())

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)

// custom format the JSON response for easy readability
_, _ = w.Write([]byte(`{
"status": "ok",
"environment": "` + env + `",
"version": "` + ver + `",
`))

if len(agents) == 0 {
_, _ = w.Write([]byte(` "agents": []` + "\n"))
} else {
_, _ = w.Write([]byte(` "agents": [` + "\n"))
for i, agent := range agents {
if i > 0 {
_, _ = w.Write([]byte(",\n"))
}
name, _ := json.Marshal(agent.Name)
_, _ = w.Write(fmt.Appendf(nil, ` {"id": "%s", "name": %s, "status": "%s"}`, agent.Id, name, agent.Status))
}
_, _ = w.Write([]byte("\n ]\n"))
data := []utils.KeyValuePair{
{Key: "status", Value: "ok"},
{Key: "environment", Value: app.Config().Environment()},
{Key: "app_version", Value: app.VersionNumber()},
{Key: "go_version", Value: runtime.Version()},
}
if ns, ok := app.KubernetesNamespace(); ok {
data = append(data, utils.KeyValuePair{Key: "kubernetes_namespace", Value: ns})
}

_, _ = w.Write([]byte("}\n"))
jsonBytes, err := utils.MakeJsonObject(data, true)
if err != nil {
logger.Err(r.Context(), err).Msg("Failed to serialize health check response.")
w.WriteHeader(http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(jsonBytes)
})
56 changes: 56 additions & 0 deletions runtime/utils/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,59 @@ func JsonDeserialize(data []byte, v any) error {
dec.UseNumber()
return dec.Decode(v)
}

type KeyValuePair struct {
Key string
Value any
}

// MakeJsonObject creates a JSON object from the given key-value pairs.
func MakeJsonObject(pairs []KeyValuePair, pretty bool) ([]byte, error) {
var buf bytes.Buffer

if pretty {
buf.WriteString("{\n")
for i, kv := range pairs {
keyBytes, err := json.Marshal(kv.Key)
if err != nil {
return nil, err
}
valBytes, err := json.Marshal(kv.Value)
if err != nil {
return nil, err
}

buf.WriteString(" ")
buf.Write(keyBytes)
buf.WriteString(": ")
buf.Write(valBytes)
if i < len(pairs)-1 {
buf.WriteByte(',')
}
buf.WriteByte('\n')
}
buf.WriteString("}\n")
} else {
buf.WriteByte('{')
for i, kv := range pairs {
keyBytes, err := json.Marshal(kv.Key)
if err != nil {
return nil, err
}
valBytes, err := json.Marshal(kv.Value)
if err != nil {
return nil, err
}

buf.Write(keyBytes)
buf.WriteByte(':')
buf.Write(valBytes)
if i < len(pairs)-1 {
buf.WriteByte(',')
}
}
buf.WriteByte('}')
}

return buf.Bytes(), nil
}
Loading