iop/apps/node/cmd/node/quota_probe.go
toki deea59d035 feat: task routing, orchestration, quota probe, and skill updates
- add finalize-task-routing skill with policy and test scripts
- add execution target selection scripts and tests for orchestrate-agent-task-loop
- add quota probe adapter and CLI status for node app
- update plan, finalize-task-routing, and orchestration skill files
- add agent-task archive for runtime target selector
- update stream-evidence-gate-core milestone
2026-07-25 19:33:17 +09:00

107 lines
3 KiB
Go

package main
import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/spf13/cobra"
"iop/apps/node/internal/adapters/cli/status"
"iop/packages/go/config"
)
var quotaCheckUsage = status.CheckUsage
func quotaProbeCmd() *cobra.Command {
var target string
var command string
var requiredCaps []string
var checkedAt string
c := &cobra.Command{
Use: "quota-probe",
Short: "Emit normalized CLI quota evidence for the dispatcher",
Hidden: true,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
target = strings.TrimSpace(target)
command = strings.TrimSpace(command)
if target == "" {
return fmt.Errorf("--target must be non-empty")
}
if command == "" {
return fmt.Errorf("--command must be non-empty")
}
caps, err := normalizeRequiredCaps(requiredCaps)
if err != nil {
return err
}
at, err := parseQuotaCheckedAt(checkedAt)
if err != nil {
return err
}
adapter := quotaAdapter(command)
usage, checkErr := quotaCheckUsage(cmd.Context(), target, config.CLIProfileConf{Command: command})
snapshot := status.NormalizeQuotaSnapshot(adapter, target, caps, at, usage, checkErr)
return json.NewEncoder(cmd.OutOrStdout()).Encode(snapshot)
},
}
c.Flags().StringVar(&target, "target", "", "canonical execution target")
c.Flags().StringVar(&command, "command", "", "CLI command used for usage checking")
c.Flags().StringSliceVar(&requiredCaps, "required-cap", nil, "required cap: overall or model:<name> (repeatable)")
c.Flags().StringVar(&checkedAt, "checked-at", "", "RFC3339 observation time (defaults to now)")
_ = c.MarkFlagRequired("target")
_ = c.MarkFlagRequired("command")
return c
}
func normalizeRequiredCaps(values []string) ([]string, error) {
if len(values) == 0 {
return nil, fmt.Errorf("at least one --required-cap is required")
}
caps := make([]string, 0, len(values))
seen := make(map[string]struct{}, len(values))
for _, value := range values {
cap := strings.TrimSpace(value)
if cap != "overall" {
model, ok := strings.CutPrefix(cap, "model:")
if !ok || strings.TrimSpace(model) == "" {
return nil, fmt.Errorf("invalid required cap %q; use overall or model:<name>", value)
}
cap = "model:" + strings.TrimSpace(model)
}
key := strings.ToLower(cap)
if _, exists := seen[key]; exists {
return nil, fmt.Errorf("duplicate required cap %q", value)
}
seen[key] = struct{}{}
caps = append(caps, cap)
}
return caps, nil
}
func parseQuotaCheckedAt(value string) (time.Time, error) {
if value == "" {
return time.Now().UTC(), nil
}
parsed, err := time.Parse(time.RFC3339, value)
if err != nil {
return time.Time{}, fmt.Errorf("invalid --checked-at: %w", err)
}
return parsed.UTC(), nil
}
func quotaAdapter(command string) string {
switch strings.ToLower(filepath.Base(command)) {
case "agy", "antigravity":
return "agy"
case "claude":
return "claude"
case "codex":
return "codex"
default:
return filepath.Base(command)
}
}