package opsconsole import ( "context" "fmt" "io" "sort" "strconv" "strings" "time" edgeservice "iop/apps/edge/internal/service" iop "iop/proto/gen/iop" ) func BuildNodeCommandRequest(adapter, targetName, sessionID string, timeoutSec int) (*iop.NodeCommandRequest, string) { req := edgeservice.BuildUsageStatusRequest(adapter, targetName, sessionID, timeoutSec) return req, req.GetRequestId() } func StatusWaitTimeout(req *iop.NodeCommandRequest) time.Duration { return edgeservice.StatusWaitTimeout(req) } func SendStatus(ctx context.Context, edgeSvc *edgeservice.Service, out io.Writer, target *TargetState) error { snap, err := edgeSvc.ResolveNodeSnapshot(target.NodeRef) if err != nil { return err } sessionID := edgeservice.NormalizeSessionID(target.SessionID) fmt.Fprintf(out, "[edge] sent command=status node=%s adapter=%s target=%s session=%s\n", snap.Label, target.Adapter, target.Target, sessionID) result, err := edgeSvc.UsageStatus(ctx, edgeservice.UsageStatusRequest{ NodeRef: target.NodeRef, Adapter: target.Adapter, Target: target.Target, SessionID: target.SessionID, TimeoutSec: target.TimeoutSec, }) if err != nil { return err } FormatUsageStatus(out, result.NodeLabel, result.Adapter, result.Target, result.SessionID, result.UsageStatus) return nil } // SendCapabilities dispatches a CAPABILITIES command and renders the result. func SendCapabilities(ctx context.Context, edgeSvc *edgeservice.Service, out io.Writer, target *TargetState) error { return sendNodeCommandView(ctx, out, target, "capabilities", edgeSvc.Capabilities, ) } // SendSessionList dispatches a SESSION_LIST command and renders the result. func SendSessionList(ctx context.Context, edgeSvc *edgeservice.Service, out io.Writer, target *TargetState) error { return sendNodeCommandView(ctx, out, target, "sessions", edgeSvc.SessionList, ) } // SendTransportStatus dispatches a TRANSPORT_STATUS command and renders the result. func SendTransportStatus(ctx context.Context, edgeSvc *edgeservice.Service, out io.Writer, target *TargetState) error { return sendNodeCommandView(ctx, out, target, "transport", edgeSvc.TransportStatus, ) } type nodeCommandFunc func(context.Context, edgeservice.NodeCommandRequestSpec) (edgeservice.NodeCommandView, error) func sendNodeCommandView(ctx context.Context, out io.Writer, target *TargetState, label string, send nodeCommandFunc) error { view, err := send(ctx, edgeservice.NodeCommandRequestSpec{ NodeRef: target.NodeRef, Adapter: target.Adapter, Target: target.Target, SessionID: target.SessionID, TimeoutSec: target.TimeoutSec, }) if err != nil { return err } FormatNodeCommandView(out, label, view) return nil } // FormatNodeCommandView renders a node command result map. For SESSION_LIST // results with structured session.N.* keys, a human-readable grouped layout // is used. All other commands fall back to stable sorted key output. func FormatNodeCommandView(out io.Writer, label string, view edgeservice.NodeCommandView) { fmt.Fprintf(out, "[%s-%s] adapter=%s target=%s session=%s\n", view.NodeLabel, label, view.Adapter, view.Target, view.SessionID) if len(view.Result) == 0 { fmt.Fprintf(out, "no %s payload returned\n", label) return } if label == "sessions" && hasSessionKeys(view.Result) { renderStructuredSessions(out, view.Result) return } keys := make([]string, 0, len(view.Result)) for k := range view.Result { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fmt.Fprintf(out, " %s = %s\n", k, view.Result[k]) } } func hasSessionKeys(result map[string]string) bool { _, ok := result["session.0.mode"] return ok } func renderStructuredSessions(out io.Writer, result map[string]string) { countStr := result["count"] count := 0 if n, err := strconv.Atoi(countStr); err == nil { count = n } fmt.Fprintf(out, "sessions: %s\n", countStr) for i := 0; i < count; i++ { prefix := fmt.Sprintf("session.%d.", i) fmt.Fprintf(out, " [%d] mode=%s target=%s session=%s\n", i, result[prefix+"mode"], result[prefix+"target"], result[prefix+"session_id"]) } } func FormatUsageStatus(out io.Writer, nodeAlias, adapterName, targetName, sessionID string, status *iop.AgentUsageStatus) { fmt.Fprintf(out, "[%s-status] adapter=%s target=%s session=%s\n", nodeAlias, adapterName, targetName, sessionID) if status == nil { fmt.Fprintln(out, "no usage status provided") return } hasParsedLimits := false dailyLabel := usageStatusLabel(status.GetMetadata(), "daily_label", "Daily limit") if status.GetDailyLimit() != "" { fmt.Fprintf(out, "%s: %s remaining%s\n", dailyLabel, status.GetDailyLimit(), resetSuffix(status.GetDailyResetTime())) hasParsedLimits = true } weeklyLabel := usageStatusLabel(status.GetMetadata(), "weekly_label", "Weekly limit") if status.GetWeeklyLimit() != "" { fmt.Fprintf(out, "%s: %s remaining%s\n", weeklyLabel, status.GetWeeklyLimit(), resetSuffix(status.GetWeeklyResetTime())) hasParsedLimits = true } metadata := status.GetMetadata() if used := usageStatusMetadata(metadata, "used_percent"); used != "" { fmt.Fprintf(out, "Used: %s\n", used) } if limit := usageStatusMetadata(metadata, "usage_limit"); limit != "" { fmt.Fprintf(out, "Usage limit: %s\n", limit) } if formatModelUsage(out, metadata) { hasParsedLimits = true } if formatClaudeStatus(out, metadata) { hasParsedLimits = true } if !hasParsedLimits { parseStatus := usageStatusMetadata(metadata, "parse_status") if status.GetRawOutput() == "" { if parseStatus != "" { fmt.Fprintf(out, "(parse_status=%s) raw output did not include parsed limits and was empty\n", parseStatus) } else { fmt.Fprintln(out, "raw output did not include parsed limits and was empty") } } else { if parseStatus != "" { fmt.Fprintf(out, "(parse_status=%s) raw output did not include parsed limits:\n", parseStatus) } else { fmt.Fprintln(out, "raw output did not include parsed limits:") } lines := strings.Split(status.GetRawOutput(), "\n") for i, line := range lines { if i >= 5 { fmt.Fprintln(out, "...") break } fmt.Fprintln(out, line) } } } } func formatClaudeStatus(out io.Writer, metadata map[string]string) bool { if usageStatusMetadata(metadata, "status_kind") != "claude" { return false } rows := []struct { label string key string }{ {"version", "claude_status_version"}, {"login_method", "claude_status_login_method"}, {"model", "claude_status_model"}, {"cwd", "claude_status_cwd"}, {"session_id", "claude_status_session_id"}, } printed := false fmt.Fprintln(out, "Claude status:") for _, row := range rows { if value := usageStatusMetadata(metadata, row.key); value != "" { fmt.Fprintf(out, " %s = %s\n", row.label, value) printed = true } } return printed } func usageStatusLabel(metadata map[string]string, key, fallback string) string { if metadata != nil { if label, ok := metadata[key]; ok { return label } } return fallback } func usageStatusMetadata(metadata map[string]string, key string) string { if metadata == nil { return "" } return metadata[key] } func formatModelUsage(out io.Writer, metadata map[string]string) bool { countRaw := usageStatusMetadata(metadata, "model_usage_count") if countRaw == "" { return false } count, err := strconv.Atoi(countRaw) if err != nil || count <= 0 { return false } printed := false for i := 0; i < count; i++ { prefix := fmt.Sprintf("model_usage_%d", i) name := usageStatusMetadata(metadata, prefix+"_name") if name == "" { continue } used := usageStatusMetadata(metadata, prefix+"_used_percent") reset := usageStatusMetadata(metadata, prefix+"_reset") if !printed { fmt.Fprintln(out, "Model usage:") printed = true } switch { case used != "" && reset != "": fmt.Fprintf(out, " %s: %s used (resets %s)\n", name, used, reset) case used != "": fmt.Fprintf(out, " %s: %s used\n", name, used) case reset != "": fmt.Fprintf(out, " %s: resets %s\n", name, reset) default: fmt.Fprintf(out, " %s\n", name) } } return printed } func resetSuffix(resetTime string) string { if resetTime == "" { return "" } return " (resets " + resetTime + ")" }