Files
goclaw/cmd/traces_paths.go

170 lines
4.9 KiB
Go

package cmd
import (
"fmt"
"net/url"
"strconv"
"strings"
"github.com/nextlevelbuilder/goclaw/internal/store"
)
type traceDataForCLI = store.TraceData
type spanDataForCLI = store.SpanData
type timelineItemForCLI = store.RunTimelineItem
type traceListOptions struct {
Query string
AgentID string
UserID string
SessionKey string
Status string
Channel string
AgentQuery string
ChannelQuery string
ToolName string
From string
To string
Since string
Until string
HasToolCalls string
MinInputTokens int
MaxInputTokens int
MinOutputTokens int
MaxOutputTokens int
MinToolCalls int
MaxToolCalls int
Limit int
Offset int
}
type traceFollowOptions struct {
AgentID string
SessionKey string
Status string
Channel string
UserID string
Since string
Limit int
IncludeSpans bool
}
type traceTimelineOptions struct {
Limit int
Offset int
}
type traceListResponse struct {
Traces []traceDataForCLI `json:"traces"`
Total int `json:"total"`
Limit int `json:"limit"`
Offset int `json:"offset"`
}
type traceDetailResponse struct {
Trace traceDataForCLI `json:"trace"`
Spans []spanDataForCLI `json:"spans"`
}
type traceFollowResponse struct {
Traces []traceDataForCLI `json:"traces"`
SpansByTraceID map[string][]spanDataForCLI `json:"spans_by_trace_id"`
ServerTime string `json:"server_time"`
NextSince string `json:"next_since"`
Limit int `json:"limit"`
}
type traceTimelineResponse struct {
RunID string `json:"run_id"`
SessionKey string `json:"session_key"`
Items []timelineItemForCLI `json:"items"`
Limit int `json:"limit"`
Offset int `json:"offset"`
}
func buildTraceListPath(opts traceListOptions) string {
values := url.Values{}
addQuery(values, "q", opts.Query)
addQuery(values, "agent_id", opts.AgentID)
addQuery(values, "user_id", opts.UserID)
addQuery(values, "session_key", opts.SessionKey)
addQuery(values, "status", opts.Status)
addQuery(values, "channel", opts.Channel)
addQuery(values, "agent", opts.AgentQuery)
addQuery(values, "channel_query", opts.ChannelQuery)
addQuery(values, "tool_name", opts.ToolName)
addQuery(values, "from", firstNonEmpty(opts.From, opts.Since))
addQuery(values, "to", firstNonEmpty(opts.To, opts.Until))
addQuery(values, "has_tool_calls", opts.HasToolCalls)
addIntQuery(values, "min_input_tokens", opts.MinInputTokens)
addIntQuery(values, "max_input_tokens", opts.MaxInputTokens)
addIntQuery(values, "min_output_tokens", opts.MinOutputTokens)
addIntQuery(values, "max_output_tokens", opts.MaxOutputTokens)
addIntQuery(values, "min_tool_calls", opts.MinToolCalls)
addIntQuery(values, "max_tool_calls", opts.MaxToolCalls)
addIntQuery(values, "limit", opts.Limit)
addIntQuery(values, "offset", opts.Offset)
return pathWithQuery("/v1/traces", values)
}
func buildTraceFollowPath(opts traceFollowOptions) (string, error) {
if strings.TrimSpace(opts.SessionKey) == "" && strings.TrimSpace(opts.AgentID) == "" {
return "", fmt.Errorf("traces follow requires --session or --agent-id")
}
values := url.Values{}
addQuery(values, "agent_id", opts.AgentID)
addQuery(values, "session_key", opts.SessionKey)
addQuery(values, "status", opts.Status)
addQuery(values, "channel", opts.Channel)
addQuery(values, "user_id", opts.UserID)
addQuery(values, "since", opts.Since)
addIntQuery(values, "limit", opts.Limit)
if opts.IncludeSpans {
values.Set("include_spans", "true")
}
return pathWithQuery("/v1/traces/follow", values), nil
}
func buildTraceTimelinePath(runID, sessionKey string, opts traceTimelineOptions) string {
values := url.Values{}
addQuery(values, "session_key", sessionKey)
addIntQuery(values, "limit", opts.Limit)
addIntQuery(values, "offset", opts.Offset)
return pathWithQuery("/v1/runs/"+url.PathEscape(runID)+"/timeline", values)
}
func traceDetailPath(traceID string) string {
return "/v1/traces/" + url.PathEscape(traceID)
}
func traceExportPath(traceID string) string {
return "/v1/traces/" + url.PathEscape(traceID) + "/export"
}
func traceRunIDFromDetail(detail traceDetailResponse) (string, error) {
runID := strings.TrimSpace(detail.Trace.RunID)
if runID == "" {
return "", fmt.Errorf("trace has no run_id; timeline is unavailable")
}
return runID, nil
}
func addQuery(values url.Values, key, value string) {
if trimmed := strings.TrimSpace(value); trimmed != "" {
values.Set(key, trimmed)
}
}
func addIntQuery(values url.Values, key string, value int) {
if value > 0 {
values.Set(key, strconv.Itoa(value))
}
}
func pathWithQuery(path string, values url.Values) string {
if len(values) == 0 {
return path
}
return path + "?" + values.Encode()
}