agent/pkg/agentic/platform.go
Virgil 57ee930717 feat(agentic): add fleet event SSE surface
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-31 19:04:06 +00:00

1098 lines
34 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
package agentic
import (
"bufio"
"context"
"io"
"net/http"
"time"
core "dappco.re/go/core"
)
// node := agentic.FleetNode{AgentID: "charon", Platform: "linux", Status: "online"}
type FleetNode struct {
ID int `json:"id"`
WorkspaceID int `json:"workspace_id,omitempty"`
AgentID string `json:"agent_id"`
Platform string `json:"platform"`
Models []string `json:"models,omitempty"`
Capabilities []string `json:"capabilities,omitempty"`
Status string `json:"status"`
ComputeBudget map[string]any `json:"compute_budget,omitempty"`
CurrentTaskID int `json:"current_task_id,omitempty"`
LastHeartbeatAt string `json:"last_heartbeat_at,omitempty"`
RegisteredAt string `json:"registered_at,omitempty"`
}
// task := agentic.FleetTask{ID: 7, Repo: "go-io", Task: "Fix tests", Status: "assigned"}
type FleetTask struct {
ID int `json:"id"`
WorkspaceID int `json:"workspace_id,omitempty"`
FleetNodeID int `json:"fleet_node_id,omitempty"`
Repo string `json:"repo"`
Branch string `json:"branch,omitempty"`
Task string `json:"task"`
Template string `json:"template,omitempty"`
AgentModel string `json:"agent_model,omitempty"`
Status string `json:"status"`
Result map[string]any `json:"result,omitempty"`
Findings []map[string]any `json:"findings,omitempty"`
Changes map[string]any `json:"changes,omitempty"`
Report map[string]any `json:"report,omitempty"`
StartedAt string `json:"started_at,omitempty"`
CompletedAt string `json:"completed_at,omitempty"`
}
// out := agentic.FleetNodesOutput{Total: 2, Nodes: []agentic.FleetNode{{AgentID: "charon"}}}
type FleetNodesOutput struct {
Total int `json:"total"`
Nodes []FleetNode `json:"nodes"`
}
// stats := agentic.FleetStats{NodesOnline: 2, TasksToday: 5}
type FleetStats struct {
NodesOnline int `json:"nodes_online"`
TasksToday int `json:"tasks_today"`
TasksWeek int `json:"tasks_week"`
ReposTouched int `json:"repos_touched"`
FindingsTotal int `json:"findings_total"`
ComputeHours int `json:"compute_hours"`
}
// event := agentic.FleetEvent{Type: "task.assigned", AgentID: "charon", Repo: "core/go-io"}
type FleetEvent struct {
Type string `json:"type,omitempty"`
Event string `json:"event,omitempty"`
AgentID string `json:"agent_id,omitempty"`
TaskID int `json:"task_id,omitempty"`
Repo string `json:"repo,omitempty"`
Branch string `json:"branch,omitempty"`
Status string `json:"status,omitempty"`
ReceivedAt string `json:"received_at,omitempty"`
Payload map[string]any `json:"payload,omitempty"`
}
// out := agentic.FleetEventOutput{Success: true, Event: agentic.FleetEvent{Type: "task.assigned"}}
type FleetEventOutput struct {
Success bool `json:"success"`
Event FleetEvent `json:"event"`
Raw string `json:"raw,omitempty"`
}
// status := agentic.SyncStatusOutput{AgentID: "charon", Status: "online"}
type SyncStatusOutput struct {
AgentID string `json:"agent_id"`
Status string `json:"status"`
LastPushAt string `json:"last_push_at,omitempty"`
LastPullAt string `json:"last_pull_at,omitempty"`
Queued int `json:"queued"`
ContextCount int `json:"context_count"`
RemoteError string `json:"remote_error,omitempty"`
}
// balance := agentic.CreditBalance{AgentID: "charon", Balance: 12}
type CreditBalance struct {
AgentID string `json:"agent_id"`
Balance int `json:"balance"`
Entries int `json:"entries"`
}
// entry := agentic.CreditEntry{ID: 4, TaskType: "fleet-task", Amount: 2}
type CreditEntry struct {
ID int `json:"id"`
WorkspaceID int `json:"workspace_id,omitempty"`
FleetNodeID int `json:"fleet_node_id,omitempty"`
TaskType string `json:"task_type"`
Amount int `json:"amount"`
BalanceAfter int `json:"balance_after"`
Description string `json:"description,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
}
// out := agentic.CreditsHistoryOutput{Total: 1, Entries: []agentic.CreditEntry{{ID: 1}}}
type CreditsHistoryOutput struct {
Total int `json:"total"`
Entries []CreditEntry `json:"entries"`
}
// caps := agentic.SubscriptionCapabilities{Available: []string{"claude", "openai"}}
type SubscriptionCapabilities struct {
Providers map[string]bool `json:"providers,omitempty"`
Available []string `json:"available,omitempty"`
}
// result := c.Action("agentic.sync.status").Run(ctx, core.NewOptions())
func (s *PrepSubsystem) handleSyncStatus(ctx context.Context, options core.Options) core.Result {
agentID := optionStringValue(options, "agent_id", "agent-id", "_arg")
if agentID == "" {
agentID = AgentName()
}
output := SyncStatusOutput{
AgentID: agentID,
Status: "offline",
Queued: len(readSyncQueue()),
ContextCount: len(readSyncContext()),
}
localStatus := readSyncStatusState()
if !localStatus.LastPushAt.IsZero() {
output.LastPushAt = localStatus.LastPushAt.Format(time.RFC3339)
}
if !localStatus.LastPullAt.IsZero() {
output.LastPullAt = localStatus.LastPullAt.Format(time.RFC3339)
}
if s.syncToken() == "" {
return core.Result{Value: output, OK: true}
}
path := appendQueryParam("/v1/agent/status", "agent_id", agentID)
result := s.platformPayload(ctx, "agentic.sync.status", "GET", path, nil)
if !result.OK {
err, _ := result.Value.(error)
if err != nil {
output.RemoteError = err.Error()
}
return core.Result{Value: output, OK: true}
}
data := payloadResourceMap(result.Value.(map[string]any), "status")
if len(data) == 0 {
return core.Result{Value: output, OK: true}
}
if remoteAgentID := stringValue(data["agent_id"]); remoteAgentID != "" {
output.AgentID = remoteAgentID
}
output.Status = stringValue(data["status"])
if lastPushAt := stringValue(data["last_push_at"]); lastPushAt != "" {
output.LastPushAt = lastPushAt
}
if lastPullAt := stringValue(data["last_pull_at"]); lastPullAt != "" {
output.LastPullAt = lastPullAt
}
if queued, ok := intValueOK(data["queued"]); ok {
output.Queued = queued
}
if contextCount, ok := intValueOK(data["context_count"]); ok {
output.ContextCount = contextCount
}
if output.Status == "" {
output.Status = "online"
}
return core.Result{Value: output, OK: true}
}
// result := c.Action("agentic.fleet.register").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"}))
func (s *PrepSubsystem) handleFleetRegister(ctx context.Context, options core.Options) core.Result {
agentID := optionStringValue(options, "agent_id", "agent-id", "_arg")
if agentID == "" {
return core.Result{Value: core.E("agentic.fleet.register", "agent_id is required", nil), OK: false}
}
platform := optionStringValue(options, "platform")
if platform == "" {
platform = "unknown"
}
body := map[string]any{
"agent_id": agentID,
"platform": platform,
}
if models := optionStringSliceValue(options, "models"); len(models) > 0 {
body["models"] = models
}
if capabilities := optionStringSliceValue(options, "capabilities"); len(capabilities) > 0 {
body["capabilities"] = capabilities
}
result := s.platformPayload(ctx, "agentic.fleet.register", "POST", "/v1/fleet/register", body)
if !result.OK {
return result
}
return core.Result{Value: parseFleetNode(payloadResourceMap(result.Value.(map[string]any), "node")), OK: true}
}
// result := c.Action("agentic.fleet.heartbeat").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"}))
func (s *PrepSubsystem) handleFleetHeartbeat(ctx context.Context, options core.Options) core.Result {
agentID := optionStringValue(options, "agent_id", "agent-id", "_arg")
status := optionStringValue(options, "status")
if agentID == "" || status == "" {
return core.Result{Value: core.E("agentic.fleet.heartbeat", "agent_id and status are required", nil), OK: false}
}
body := map[string]any{
"agent_id": agentID,
"status": status,
}
if budget := optionAnyMapValue(options, "compute_budget", "compute-budget"); len(budget) > 0 {
body["compute_budget"] = budget
}
result := s.platformPayload(ctx, "agentic.fleet.heartbeat", "POST", "/v1/fleet/heartbeat", body)
if !result.OK {
return result
}
return core.Result{Value: parseFleetNode(payloadResourceMap(result.Value.(map[string]any), "node")), OK: true}
}
// result := c.Action("agentic.fleet.deregister").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"}))
func (s *PrepSubsystem) handleFleetDeregister(ctx context.Context, options core.Options) core.Result {
agentID := optionStringValue(options, "agent_id", "agent-id", "_arg")
if agentID == "" {
return core.Result{Value: core.E("agentic.fleet.deregister", "agent_id is required", nil), OK: false}
}
result := s.platformPayload(ctx, "agentic.fleet.deregister", "POST", "/v1/fleet/deregister", map[string]any{
"agent_id": agentID,
})
if !result.OK {
return result
}
return core.Result{Value: map[string]any{
"agent_id": agentID,
"deregistered": true,
}, OK: true}
}
// result := c.Action("agentic.fleet.nodes").Run(ctx, core.NewOptions())
func (s *PrepSubsystem) handleFleetNodes(ctx context.Context, options core.Options) core.Result {
path := "/v1/fleet/nodes"
path = appendQueryParam(path, "status", optionStringValue(options, "status"))
path = appendQueryParam(path, "platform", optionStringValue(options, "platform"))
result := s.platformPayload(ctx, "agentic.fleet.nodes", "GET", path, nil)
if !result.OK {
return result
}
return core.Result{Value: parseFleetNodesOutput(result.Value.(map[string]any)), OK: true}
}
// result := c.Action("agentic.fleet.task.assign").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"}))
func (s *PrepSubsystem) handleFleetAssignTask(ctx context.Context, options core.Options) core.Result {
agentID := optionStringValue(options, "agent_id", "agent-id", "_arg")
repo := optionStringValue(options, "repo")
task := optionStringValue(options, "task")
if agentID == "" || repo == "" || task == "" {
return core.Result{Value: core.E("agentic.fleet.task.assign", "agent_id, repo, and task are required", nil), OK: false}
}
body := map[string]any{
"agent_id": agentID,
"repo": repo,
"task": task,
}
if branch := optionStringValue(options, "branch"); branch != "" {
body["branch"] = branch
}
if template := optionStringValue(options, "template"); template != "" {
body["template"] = template
}
if agentModel := optionStringValue(options, "agent_model", "agent-model"); agentModel != "" {
body["agent_model"] = agentModel
}
result := s.platformPayload(ctx, "agentic.fleet.task.assign", "POST", "/v1/fleet/task/assign", body)
if !result.OK {
return result
}
return core.Result{Value: parseFleetTask(payloadResourceMap(result.Value.(map[string]any), "task")), OK: true}
}
// result := c.Action("agentic.fleet.task.complete").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"}))
func (s *PrepSubsystem) handleFleetCompleteTask(ctx context.Context, options core.Options) core.Result {
agentID := optionStringValue(options, "agent_id", "agent-id", "_arg")
taskID := optionIntValue(options, "task_id", "task-id")
if agentID == "" || taskID == 0 {
return core.Result{Value: core.E("agentic.fleet.task.complete", "agent_id and task_id are required", nil), OK: false}
}
body := map[string]any{
"agent_id": agentID,
"task_id": taskID,
}
if resultMap := optionAnyMapValue(options, "result"); len(resultMap) > 0 {
body["result"] = resultMap
}
if findings := optionAnyMapSliceValue(options, "findings"); len(findings) > 0 {
body["findings"] = findings
}
if changes := optionAnyMapValue(options, "changes"); len(changes) > 0 {
body["changes"] = changes
}
if report := optionAnyMapValue(options, "report"); len(report) > 0 {
body["report"] = report
}
result := s.platformPayload(ctx, "agentic.fleet.task.complete", "POST", "/v1/fleet/task/complete", body)
if !result.OK {
return result
}
return core.Result{Value: parseFleetTask(payloadResourceMap(result.Value.(map[string]any), "task")), OK: true}
}
// result := c.Action("agentic.fleet.task.next").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"}))
func (s *PrepSubsystem) handleFleetNextTask(ctx context.Context, options core.Options) core.Result {
agentID := optionStringValue(options, "agent_id", "agent-id", "_arg")
if agentID == "" {
return core.Result{Value: core.E("agentic.fleet.task.next", "agent_id is required", nil), OK: false}
}
path := appendQueryParam("/v1/fleet/task/next", "agent_id", agentID)
path = appendQuerySlice(path, "capabilities[]", optionStringSliceValue(options, "capabilities"))
result := s.platformPayload(ctx, "agentic.fleet.task.next", "GET", path, nil)
if !result.OK {
return result
}
data := payloadResourceMap(result.Value.(map[string]any), "task")
if len(data) == 0 {
var task *FleetTask
return core.Result{Value: task, OK: true}
}
task := parseFleetTask(data)
return core.Result{Value: &task, OK: true}
}
// result := c.Action("agentic.fleet.stats").Run(ctx, core.NewOptions())
func (s *PrepSubsystem) handleFleetStats(ctx context.Context, options core.Options) core.Result {
result := s.platformPayload(ctx, "agentic.fleet.stats", "GET", "/v1/fleet/stats", nil)
if !result.OK {
return result
}
return core.Result{Value: parseFleetStats(payloadResourceMap(result.Value.(map[string]any), "stats")), OK: true}
}
// result := c.Action("agentic.fleet.events").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"}))
func (s *PrepSubsystem) handleFleetEvents(ctx context.Context, options core.Options) core.Result {
agentID := optionStringValue(options, "agent_id", "agent-id", "_arg")
path := "/v1/fleet/events"
path = appendQueryParam(path, "agent_id", agentID)
result := s.platformEventPayload(ctx, "agentic.fleet.events", path)
if !result.OK {
return result
}
output, err := parseFleetEventOutput(result.Value.(map[string]any))
if err != nil {
return core.Result{Value: err, OK: false}
}
return core.Result{Value: output, OK: true}
}
// result := c.Action("agentic.credits.award").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"}))
func (s *PrepSubsystem) handleCreditsAward(ctx context.Context, options core.Options) core.Result {
agentID := optionStringValue(options, "agent_id", "agent-id", "_arg")
taskType := optionStringValue(options, "task_type", "task-type")
amount := optionIntValue(options, "amount")
if agentID == "" || taskType == "" || amount == 0 {
return core.Result{Value: core.E("agentic.credits.award", "agent_id, task_type, and amount are required", nil), OK: false}
}
body := map[string]any{
"agent_id": agentID,
"task_type": taskType,
"amount": amount,
}
if fleetNodeID := optionIntValue(options, "fleet_node_id", "fleet-node-id"); fleetNodeID > 0 {
body["fleet_node_id"] = fleetNodeID
}
if description := optionStringValue(options, "description"); description != "" {
body["description"] = description
}
result := s.platformPayload(ctx, "agentic.credits.award", "POST", "/v1/credits/award", body)
if !result.OK {
return result
}
return core.Result{Value: parseCreditEntry(payloadResourceMap(result.Value.(map[string]any), "entry")), OK: true}
}
// result := c.Action("agentic.credits.balance").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"}))
func (s *PrepSubsystem) handleCreditsBalance(ctx context.Context, options core.Options) core.Result {
agentID := optionStringValue(options, "agent_id", "agent-id", "_arg")
if agentID == "" {
return core.Result{Value: core.E("agentic.credits.balance", "agent_id is required", nil), OK: false}
}
path := core.Concat("/v1/credits/balance/", agentID)
result := s.platformPayload(ctx, "agentic.credits.balance", "GET", path, nil)
if !result.OK {
return result
}
return core.Result{Value: parseCreditBalance(payloadResourceMap(result.Value.(map[string]any), "balance")), OK: true}
}
// result := c.Action("agentic.credits.history").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"}))
func (s *PrepSubsystem) handleCreditsHistory(ctx context.Context, options core.Options) core.Result {
agentID := optionStringValue(options, "agent_id", "agent-id", "_arg")
if agentID == "" {
return core.Result{Value: core.E("agentic.credits.history", "agent_id is required", nil), OK: false}
}
path := core.Concat("/v1/credits/history/", agentID)
if limit := optionIntValue(options, "limit"); limit > 0 {
path = appendQueryParam(path, "limit", core.Sprint(limit))
}
result := s.platformPayload(ctx, "agentic.credits.history", "GET", path, nil)
if !result.OK {
return result
}
return core.Result{Value: parseCreditsHistoryOutput(result.Value.(map[string]any)), OK: true}
}
// result := c.Action("agentic.subscription.detect").Run(ctx, core.NewOptions())
func (s *PrepSubsystem) handleSubscriptionDetect(ctx context.Context, options core.Options) core.Result {
body := map[string]any{}
if apiKeys := optionStringMapValue(options, "api_keys", "api-keys"); len(apiKeys) > 0 {
body["api_keys"] = apiKeys
}
result := s.platformPayload(ctx, "agentic.subscription.detect", "POST", "/v1/subscription/detect", body)
if !result.OK {
return result
}
return core.Result{Value: parseSubscriptionCapabilities(payloadResourceMap(result.Value.(map[string]any), "capabilities", "subscription")), OK: true}
}
// result := c.Action("agentic.subscription.budget").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"}))
func (s *PrepSubsystem) handleSubscriptionBudget(ctx context.Context, options core.Options) core.Result {
agentID := optionStringValue(options, "agent_id", "agent-id", "_arg")
if agentID == "" {
return core.Result{Value: core.E("agentic.subscription.budget", "agent_id is required", nil), OK: false}
}
path := core.Concat("/v1/subscription/budget/", agentID)
result := s.platformPayload(ctx, "agentic.subscription.budget", "GET", path, nil)
if !result.OK {
return result
}
return core.Result{Value: payloadResourceMap(result.Value.(map[string]any), "budget"), OK: true}
}
// result := c.Action("agentic.subscription.budget.update").Run(ctx, core.NewOptions(core.Option{Key: "agent_id", Value: "charon"}))
func (s *PrepSubsystem) handleSubscriptionBudgetUpdate(ctx context.Context, options core.Options) core.Result {
agentID := optionStringValue(options, "agent_id", "agent-id", "_arg")
limits := optionAnyMapValue(options, "limits")
if agentID == "" || len(limits) == 0 {
return core.Result{Value: core.E("agentic.subscription.budget.update", "agent_id and limits are required", nil), OK: false}
}
path := core.Concat("/v1/subscription/budget/", agentID)
result := s.platformPayload(ctx, "agentic.subscription.budget.update", "PUT", path, map[string]any{
"limits": limits,
})
if !result.OK {
return result
}
return core.Result{Value: payloadResourceMap(result.Value.(map[string]any), "budget"), OK: true}
}
func (s *PrepSubsystem) platformPayload(ctx context.Context, action, method, path string, body any) core.Result {
token := s.syncToken()
if token == "" {
return core.Result{Value: core.E(action, "no platform API key configured", nil), OK: false}
}
bodyString := ""
if body != nil {
bodyString = core.JSONMarshalString(body)
}
requestResult := HTTPDo(ctx, method, core.Concat(s.syncAPIURL(), path), bodyString, token, "Bearer")
if !requestResult.OK {
return core.Result{Value: platformResultError(action, requestResult), OK: false}
}
var payload map[string]any
parseResult := core.JSONUnmarshalString(requestResult.Value.(string), &payload)
if !parseResult.OK {
err, _ := parseResult.Value.(error)
return core.Result{Value: core.E(action, "failed to parse platform response", err), OK: false}
}
return core.Result{Value: payload, OK: true}
}
func (s *PrepSubsystem) platformEventPayload(ctx context.Context, action, path string) core.Result {
token := s.syncToken()
if token == "" {
return core.Result{Value: core.E(action, "no platform API key configured", nil), OK: false}
}
request, err := http.NewRequestWithContext(ctx, "GET", core.Concat(s.syncAPIURL(), path), nil)
if err != nil {
return core.Result{Value: core.E(action, "create request", err), OK: false}
}
request.Header.Set("Accept", "text/event-stream, application/json")
request.Header.Set("Authorization", core.Concat("Bearer ", token))
response, err := defaultClient.Do(request)
if err != nil {
return core.Result{Value: core.E(action, "request failed", err), OK: false}
}
defer response.Body.Close()
if response.StatusCode >= 400 {
readResult := core.ReadAll(response.Body)
if !readResult.OK {
return core.Result{Value: core.E(action, core.Sprintf("HTTP %d", response.StatusCode), nil), OK: false}
}
body := core.Trim(readResult.Value.(string))
if body == "" {
return core.Result{Value: core.E(action, core.Sprintf("HTTP %d", response.StatusCode), nil), OK: false}
}
return core.Result{Value: platformResultError(action, core.Result{Value: body, OK: false}), OK: false}
}
eventBody, readErr := readFleetEventBody(response.Body)
if readErr != nil {
return core.Result{Value: core.E(action, "failed to read event stream", readErr), OK: false}
}
payload := s.eventPayloadValue(eventBody)
if len(payload) == 0 {
return core.Result{Value: core.E(action, "no fleet event payload returned", nil), OK: false}
}
return core.Result{Value: payload, OK: true}
}
func platformResultError(action string, result core.Result) error {
if err, ok := result.Value.(error); ok && err != nil {
return core.E(action, "platform request failed", err)
}
body := core.Trim(stringValue(result.Value))
if body == "" {
return core.E(action, "platform request failed", nil)
}
var payload map[string]any
if parseResult := core.JSONUnmarshalString(body, &payload); parseResult.OK {
if message := stringValue(payload["error"]); message != "" {
return core.E(action, message, nil)
}
}
return core.E(action, body, nil)
}
func payloadDataMap(payload map[string]any) map[string]any {
return anyMapValue(payload["data"])
}
func payloadDataSlice(payload map[string]any, keys ...string) []map[string]any {
if values := anyMapSliceValue(payload["data"]); len(values) > 0 {
return values
}
if data := payloadDataMap(payload); len(data) > 0 {
for _, key := range keys {
if values := anyMapSliceValue(data[key]); len(values) > 0 {
return values
}
}
}
for _, key := range keys {
if values := anyMapSliceValue(payload[key]); len(values) > 0 {
return values
}
}
return nil
}
func payloadResourceMap(payload map[string]any, keys ...string) map[string]any {
if data := payloadDataMap(payload); len(data) > 0 {
for _, key := range keys {
if values := anyMapValue(data[key]); len(values) > 0 {
return values
}
}
return data
}
for _, key := range keys {
if values := anyMapValue(payload[key]); len(values) > 0 {
return values
}
}
for key, value := range payload {
switch key {
case "data", "error", "code", "message":
continue
}
if value != nil {
return payload
}
}
return nil
}
func mapIntValue(values map[string]any, keys ...string) int {
for _, key := range keys {
if value, ok := values[key]; ok {
return intValue(value)
}
}
return 0
}
func intValueOK(value any) (int, bool) {
switch typed := value.(type) {
case int:
return typed, true
case int64:
return int(typed), true
case float64:
return int(typed), true
case string:
trimmed := core.Trim(typed)
parsed := parseInt(trimmed)
if parsed != 0 || trimmed == "0" {
return parsed, true
}
}
return 0, false
}
func parseFleetNode(values map[string]any) FleetNode {
return FleetNode{
ID: intValue(values["id"]),
WorkspaceID: intValue(values["workspace_id"]),
AgentID: stringValue(values["agent_id"]),
Platform: stringValue(values["platform"]),
Models: listValue(values["models"]),
Capabilities: listValue(values["capabilities"]),
Status: stringValue(values["status"]),
ComputeBudget: anyMapValue(values["compute_budget"]),
CurrentTaskID: intValue(values["current_task_id"]),
LastHeartbeatAt: stringValue(values["last_heartbeat_at"]),
RegisteredAt: stringValue(values["registered_at"]),
}
}
func parseFleetTask(values map[string]any) FleetTask {
return FleetTask{
ID: intValue(values["id"]),
WorkspaceID: intValue(values["workspace_id"]),
FleetNodeID: intValue(values["fleet_node_id"]),
Repo: stringValue(values["repo"]),
Branch: stringValue(values["branch"]),
Task: stringValue(values["task"]),
Template: stringValue(values["template"]),
AgentModel: stringValue(values["agent_model"]),
Status: stringValue(values["status"]),
Result: anyMapValue(values["result"]),
Findings: anyMapSliceValue(values["findings"]),
Changes: anyMapValue(values["changes"]),
Report: anyMapValue(values["report"]),
StartedAt: stringValue(values["started_at"]),
CompletedAt: stringValue(values["completed_at"]),
}
}
func parseFleetNodesOutput(payload map[string]any) FleetNodesOutput {
nodesData := payloadDataSlice(payload, "nodes")
nodes := make([]FleetNode, 0, len(nodesData))
for _, values := range nodesData {
nodes = append(nodes, parseFleetNode(values))
}
total := mapIntValue(payload, "total", "count")
if total == 0 {
total = mapIntValue(payloadDataMap(payload), "total", "count")
}
if total == 0 {
total = len(nodes)
}
return FleetNodesOutput{
Total: total,
Nodes: nodes,
}
}
func parseFleetStats(values map[string]any) FleetStats {
return FleetStats{
NodesOnline: intValue(values["nodes_online"]),
TasksToday: intValue(values["tasks_today"]),
TasksWeek: intValue(values["tasks_week"]),
ReposTouched: intValue(values["repos_touched"]),
FindingsTotal: intValue(values["findings_total"]),
ComputeHours: intValue(values["compute_hours"]),
}
}
func parseFleetEventOutput(values map[string]any) (FleetEventOutput, error) {
eventValues := payloadResourceMap(values, "event")
if len(eventValues) == 0 {
eventValues = values
}
event := parseFleetEvent(eventValues)
if event.Event == "" && event.Type == "" && event.AgentID == "" && event.Repo == "" {
return FleetEventOutput{}, core.E("parseFleetEventOutput", "fleet event payload is empty", nil)
}
return FleetEventOutput{
Success: true,
Event: event,
Raw: core.Trim(stringValue(values["raw"])),
}, nil
}
func (s *PrepSubsystem) eventPayloadValue(body string) map[string]any {
trimmed := core.Trim(body)
if trimmed == "" {
return nil
}
if core.HasPrefix(trimmed, "data: ") {
trimmed = core.Trim(core.TrimPrefix(trimmed, "data: "))
}
var payload map[string]any
if parseResult := core.JSONUnmarshalString(trimmed, &payload); parseResult.OK {
if payload != nil {
payload["raw"] = trimmed
}
return payload
}
return map[string]any{
"raw": trimmed,
}
}
func readFleetEventBody(body io.ReadCloser) (string, error) {
reader := bufio.NewReader(body)
rawLines := make([]string, 0, 4)
dataLines := make([]string, 0, 4)
for {
line, err := reader.ReadString('\n')
if line != "" {
trimmed := core.Trim(line)
if trimmed != "" {
rawLines = append(rawLines, trimmed)
if core.HasPrefix(trimmed, "data:") {
dataLines = append(dataLines, core.Trim(core.TrimPrefix(trimmed, "data:")))
}
} else if len(dataLines) > 0 {
return core.Join("\n", dataLines...), nil
}
}
if err == io.EOF {
if len(dataLines) > 0 {
return core.Join("\n", dataLines...), nil
}
return core.Join("\n", rawLines...), nil
}
if err != nil {
return "", err
}
}
}
func parseFleetEvent(values map[string]any) FleetEvent {
payload := map[string]any{}
for key, value := range values {
switch key {
case "type", "event", "agent_id", "task_id", "repo", "branch", "status", "received_at":
continue
default:
payload[key] = value
}
}
if len(payload) == 0 {
payload = nil
}
event := FleetEvent{
Type: stringValue(values["type"]),
Event: stringValue(values["event"]),
AgentID: stringValue(values["agent_id"]),
TaskID: intValue(values["task_id"]),
Repo: stringValue(values["repo"]),
Branch: stringValue(values["branch"]),
Status: stringValue(values["status"]),
ReceivedAt: stringValue(values["received_at"]),
Payload: payload,
}
if event.Event == "" {
event.Event = event.Type
}
if event.Type == "" {
event.Type = event.Event
}
return event
}
func parseCreditEntry(values map[string]any) CreditEntry {
return CreditEntry{
ID: intValue(values["id"]),
WorkspaceID: intValue(values["workspace_id"]),
FleetNodeID: intValue(values["fleet_node_id"]),
TaskType: stringValue(values["task_type"]),
Amount: intValue(values["amount"]),
BalanceAfter: intValue(values["balance_after"]),
Description: stringValue(values["description"]),
CreatedAt: stringValue(values["created_at"]),
}
}
func parseCreditBalance(values map[string]any) CreditBalance {
return CreditBalance{
AgentID: stringValue(values["agent_id"]),
Balance: intValue(values["balance"]),
Entries: intValue(values["entries"]),
}
}
func parseCreditsHistoryOutput(payload map[string]any) CreditsHistoryOutput {
entriesData := payloadDataSlice(payload, "entries", "history")
entries := make([]CreditEntry, 0, len(entriesData))
for _, values := range entriesData {
entries = append(entries, parseCreditEntry(values))
}
total := mapIntValue(payload, "total", "count")
if total == 0 {
total = mapIntValue(payloadDataMap(payload), "total", "count")
}
if total == 0 {
total = len(entries)
}
return CreditsHistoryOutput{
Total: total,
Entries: entries,
}
}
func parseSubscriptionCapabilities(values map[string]any) SubscriptionCapabilities {
capabilities := SubscriptionCapabilities{
Providers: boolMapValue(values["providers"]),
Available: listValue(values["available"]),
}
if len(capabilities.Available) == 0 && len(capabilities.Providers) > 0 {
for name, enabled := range capabilities.Providers {
if enabled {
capabilities.Available = append(capabilities.Available, name)
}
}
capabilities.Available = cleanStrings(capabilities.Available)
}
return capabilities
}
func appendQueryParam(path, key, value string) string {
value = core.Trim(value)
if value == "" {
return path
}
separator := "?"
if core.Contains(path, "?") {
separator = "&"
}
return core.Concat(path, separator, key, "=", value)
}
func appendQuerySlice(path, key string, values []string) string {
for _, value := range values {
path = appendQueryParam(path, key, value)
}
return path
}
func optionAnyMapValue(options core.Options, keys ...string) map[string]any {
for _, key := range keys {
result := options.Get(key)
if !result.OK {
continue
}
values := anyMapValue(result.Value)
if len(values) > 0 {
return values
}
}
return nil
}
func optionAnyMapSliceValue(options core.Options, keys ...string) []map[string]any {
for _, key := range keys {
result := options.Get(key)
if !result.OK {
continue
}
values := anyMapSliceValue(result.Value)
if len(values) > 0 {
return values
}
}
return nil
}
func anyMapValue(value any) map[string]any {
switch typed := value.(type) {
case map[string]any:
return typed
case map[string]string:
values := make(map[string]any, len(typed))
for key, item := range typed {
values[key] = item
}
return values
case string:
trimmed := core.Trim(typed)
if trimmed == "" {
return nil
}
if core.HasPrefix(trimmed, "{") {
var values map[string]any
if result := core.JSONUnmarshalString(trimmed, &values); result.OK {
return values
}
var stringValues map[string]string
if result := core.JSONUnmarshalString(trimmed, &stringValues); result.OK {
return anyMapValue(stringValues)
}
}
values := stringMapValue(trimmed)
if len(values) > 0 {
return anyMapValue(values)
}
}
return nil
}
func anyMapSliceValue(value any) []map[string]any {
switch typed := value.(type) {
case []map[string]any:
return typed
case []any:
values := make([]map[string]any, 0, len(typed))
for _, item := range typed {
if mapValue := anyMapValue(item); len(mapValue) > 0 {
values = append(values, mapValue)
}
}
return values
case string:
trimmed := core.Trim(typed)
if trimmed == "" {
return nil
}
if core.HasPrefix(trimmed, "[") {
var values []map[string]any
if result := core.JSONUnmarshalString(trimmed, &values); result.OK {
return values
}
var generic []any
if result := core.JSONUnmarshalString(trimmed, &generic); result.OK {
return anyMapSliceValue(generic)
}
}
}
return nil
}
func boolMapValue(value any) map[string]bool {
switch typed := value.(type) {
case map[string]bool:
return typed
case map[string]any:
values := make(map[string]bool, len(typed))
for key, item := range typed {
switch resolved := item.(type) {
case bool:
values[key] = resolved
case string:
values[key] = core.Lower(core.Trim(resolved)) == "true"
default:
values[key] = intValue(resolved) > 0
}
}
return values
case string:
trimmed := core.Trim(typed)
if trimmed == "" {
return nil
}
if core.HasPrefix(trimmed, "{") {
var values map[string]bool
if result := core.JSONUnmarshalString(trimmed, &values); result.OK {
return values
}
var generic map[string]any
if result := core.JSONUnmarshalString(trimmed, &generic); result.OK {
return boolMapValue(generic)
}
}
}
return nil
}
func listValue(value any) []string {
switch typed := value.(type) {
case map[string]any:
values := make([]string, 0, len(typed))
for key, item := range typed {
if item == true || core.Trim(stringValue(item)) != "" {
values = append(values, key)
}
}
return cleanStrings(values)
default:
return stringSliceValue(value)
}
}
func intValue(value any) int {
switch typed := value.(type) {
case int:
return typed
case int64:
return int(typed)
case float64:
return int(typed)
case string:
parsed := parseInt(typed)
if parsed != 0 || core.Trim(typed) == "0" {
return parsed
}
}
return 0
}