3434db3c59
- Add event_type and framework filters to events query endpoint - Add /agents SPA route to web-ui server - Add Agents nav link and route in frontend - Add agents page CSS (timeline, VM pills, stats panel) - Build VM status strip, activity timeline, and real-time stats - Add agentmon hook for OpenClaw (HOOK.md + handler.ts) - Add docker-compose, Dockerfile, and supporting infra files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
333 lines
7.3 KiB
Go
333 lines
7.3 KiB
Go
package sdk
|
|
|
|
import (
|
|
"time"
|
|
)
|
|
|
|
// NewSessionStart creates a session.start event.
|
|
func NewSessionStart(sessionID string, opts ...EventOption) Event {
|
|
now := time.Now()
|
|
event := map[string]any{
|
|
"schema": map[string]any{
|
|
"name": schemaName,
|
|
"version": schemaVersion,
|
|
},
|
|
"event": map[string]any{
|
|
"id": generateID(),
|
|
"type": "session.start",
|
|
"ts": now.UTC().Format(time.RFC3339Nano),
|
|
},
|
|
"correlation": map[string]any{
|
|
"session_id": sessionID,
|
|
},
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(event)
|
|
}
|
|
|
|
return event
|
|
}
|
|
|
|
// NewSessionEnd creates a session.end event.
|
|
func NewSessionEnd(sessionID string, opts ...EventOption) Event {
|
|
now := time.Now()
|
|
event := map[string]any{
|
|
"schema": map[string]any{
|
|
"name": schemaName,
|
|
"version": schemaVersion,
|
|
},
|
|
"event": map[string]any{
|
|
"id": generateID(),
|
|
"type": "session.end",
|
|
"ts": now.UTC().Format(time.RFC3339Nano),
|
|
},
|
|
"correlation": map[string]any{
|
|
"session_id": sessionID,
|
|
},
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(event)
|
|
}
|
|
|
|
return event
|
|
}
|
|
|
|
// NewRunStart creates a run.start event.
|
|
func NewRunStart(sessionID, runID string, opts ...EventOption) Event {
|
|
now := time.Now()
|
|
event := map[string]any{
|
|
"schema": map[string]any{
|
|
"name": schemaName,
|
|
"version": schemaVersion,
|
|
},
|
|
"event": map[string]any{
|
|
"id": generateID(),
|
|
"type": "run.start",
|
|
"ts": now.UTC().Format(time.RFC3339Nano),
|
|
},
|
|
"correlation": map[string]any{
|
|
"session_id": sessionID,
|
|
"run_id": runID,
|
|
},
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(event)
|
|
}
|
|
|
|
return event
|
|
}
|
|
|
|
// NewRunEnd creates a run.end event.
|
|
func NewRunEnd(sessionID, runID string, status string, durationMs int64, opts ...EventOption) Event {
|
|
now := time.Now()
|
|
event := map[string]any{
|
|
"schema": map[string]any{
|
|
"name": schemaName,
|
|
"version": schemaVersion,
|
|
},
|
|
"event": map[string]any{
|
|
"id": generateID(),
|
|
"type": "run.end",
|
|
"ts": now.UTC().Format(time.RFC3339Nano),
|
|
},
|
|
"correlation": map[string]any{
|
|
"session_id": sessionID,
|
|
"run_id": runID,
|
|
},
|
|
"payload": map[string]any{
|
|
"status": status,
|
|
"duration_ms": durationMs,
|
|
},
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(event)
|
|
}
|
|
|
|
return event
|
|
}
|
|
|
|
// NewSpanStart creates a span.start event.
|
|
func NewSpanStart(sessionID, runID, traceID, spanID string, opts ...EventOption) Event {
|
|
now := time.Now()
|
|
event := map[string]any{
|
|
"schema": map[string]any{
|
|
"name": schemaName,
|
|
"version": schemaVersion,
|
|
},
|
|
"event": map[string]any{
|
|
"id": generateID(),
|
|
"type": "span.start",
|
|
"ts": now.UTC().Format(time.RFC3339Nano),
|
|
},
|
|
"correlation": map[string]any{
|
|
"session_id": sessionID,
|
|
"run_id": runID,
|
|
"trace_id": traceID,
|
|
"span_id": spanID,
|
|
},
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(event)
|
|
}
|
|
|
|
return event
|
|
}
|
|
|
|
// NewSpanEnd creates a span.end event.
|
|
func NewSpanEnd(sessionID, runID, traceID, spanID string, status string, durationMs int64, opts ...EventOption) Event {
|
|
now := time.Now()
|
|
event := map[string]any{
|
|
"schema": map[string]any{
|
|
"name": schemaName,
|
|
"version": schemaVersion,
|
|
},
|
|
"event": map[string]any{
|
|
"id": generateID(),
|
|
"type": "span.end",
|
|
"ts": now.UTC().Format(time.RFC3339Nano),
|
|
},
|
|
"correlation": map[string]any{
|
|
"session_id": sessionID,
|
|
"run_id": runID,
|
|
"trace_id": traceID,
|
|
"span_id": spanID,
|
|
},
|
|
"payload": map[string]any{
|
|
"status": status,
|
|
"duration_ms": durationMs,
|
|
},
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(event)
|
|
}
|
|
|
|
return event
|
|
}
|
|
|
|
// NewError creates an error event.
|
|
func NewError(sessionID, runID, traceID, spanID string, errType, message string, opts ...EventOption) Event {
|
|
now := time.Now()
|
|
event := map[string]any{
|
|
"schema": map[string]any{
|
|
"name": schemaName,
|
|
"version": schemaVersion,
|
|
},
|
|
"event": map[string]any{
|
|
"id": generateID(),
|
|
"type": "error",
|
|
"ts": now.UTC().Format(time.RFC3339Nano),
|
|
},
|
|
"correlation": map[string]any{
|
|
"session_id": sessionID,
|
|
"run_id": runID,
|
|
"trace_id": traceID,
|
|
"span_id": spanID,
|
|
},
|
|
"payload": map[string]any{
|
|
"error": map[string]any{
|
|
"type": errType,
|
|
"message": message,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(event)
|
|
}
|
|
|
|
return event
|
|
}
|
|
|
|
// NewMetricSnapshot creates a metric.snapshot event.
|
|
func NewMetricSnapshot(sessionID, runID string, metrics map[string]any, opts ...EventOption) Event {
|
|
now := time.Now()
|
|
event := map[string]any{
|
|
"schema": map[string]any{
|
|
"name": schemaName,
|
|
"version": schemaVersion,
|
|
},
|
|
"event": map[string]any{
|
|
"id": generateID(),
|
|
"type": "metric.snapshot",
|
|
"ts": now.UTC().Format(time.RFC3339Nano),
|
|
},
|
|
"correlation": map[string]any{
|
|
"session_id": sessionID,
|
|
"run_id": runID,
|
|
},
|
|
"payload": map[string]any{
|
|
"metrics": metrics,
|
|
},
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(event)
|
|
}
|
|
|
|
return event
|
|
}
|
|
|
|
// EventOption is a function that modifies an event.
|
|
type EventOption func(Event)
|
|
|
|
// WithSource sets the source information on an event.
|
|
func WithSource(emitter *Emitter) EventOption {
|
|
return func(e Event) {
|
|
if event, ok := e["event"].(map[string]any); ok {
|
|
event["source"] = map[string]any{
|
|
"framework": emitter.config.Framework,
|
|
"client_id": emitter.config.ClientID,
|
|
"host": emitter.config.Host,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithAttributes adds attributes to an event.
|
|
func WithAttributes(attrs map[string]any) EventOption {
|
|
return func(e Event) {
|
|
if _, ok := e["attributes"]; !ok {
|
|
e["attributes"] = make(map[string]any)
|
|
}
|
|
if attrsMap, ok := e["attributes"].(map[string]any); ok {
|
|
for k, v := range attrs {
|
|
attrsMap[k] = v
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithSpanKind sets the span_kind attribute.
|
|
func WithSpanKind(kind string) EventOption {
|
|
return WithAttributes(map[string]any{"span_kind": kind})
|
|
}
|
|
|
|
// WithName sets the name attribute.
|
|
func WithName(name string) EventOption {
|
|
return WithAttributes(map[string]any{"name": name})
|
|
}
|
|
|
|
// WithParentSpanID sets the parent_span_id in correlation.
|
|
func WithParentSpanID(parentID string) EventOption {
|
|
return func(e Event) {
|
|
if corr, ok := e["correlation"].(map[string]any); ok {
|
|
corr["parent_span_id"] = parentID
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithPayload sets the payload on an event.
|
|
func WithPayload(payload map[string]any) EventOption {
|
|
return func(e Event) {
|
|
e["payload"] = payload
|
|
}
|
|
}
|
|
|
|
// WithSeq sets the sequence number on an event.
|
|
func WithSeq(seq int) EventOption {
|
|
return func(e Event) {
|
|
if event, ok := e["event"].(map[string]any); ok {
|
|
event["seq"] = seq
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithLLMUsage adds LLM usage information to a span.end or run.end payload.
|
|
func WithLLMUsage(model string, inputTokens, outputTokens int, costUSD float64) EventOption {
|
|
return func(e Event) {
|
|
if payload, ok := e["payload"].(map[string]any); ok {
|
|
if _, ok := payload["llm"]; !ok {
|
|
payload["llm"] = make(map[string]any)
|
|
}
|
|
if llm, ok := payload["llm"].(map[string]any); ok {
|
|
llm["model"] = model
|
|
llm["usage"] = map[string]any{
|
|
"input_tokens": inputTokens,
|
|
"output_tokens": outputTokens,
|
|
}
|
|
llm["cost"] = map[string]any{
|
|
"total_usd": costUSD,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithErrorDetails adds error details to a payload.
|
|
func WithErrorDetails(code string, retryable bool) EventOption {
|
|
return func(e Event) {
|
|
if payload, ok := e["payload"].(map[string]any); ok {
|
|
if err, ok := payload["error"].(map[string]any); ok {
|
|
err["code"] = code
|
|
err["retryable"] = retryable
|
|
}
|
|
}
|
|
}
|
|
}
|