feat(query): include session totals and stable framework names
This commit is contained in:
@@ -218,6 +218,20 @@ The `hooks/gemini/` directory contains a TypeScript handler for Gemini CLI telem
|
|||||||
|
|
||||||
Sample Gemini hook configuration lives in [hooks/gemini/hooks.json](/home/will/lab/agentmon/hooks/gemini/hooks.json). Install the handler from that directory so the `agentmon-gemini-handler` binary is available, then point Gemini CLI at the sample hook config and set `AGENTMON_INGEST_URL` to your ingest gateway.
|
Sample Gemini hook configuration lives in [hooks/gemini/hooks.json](/home/will/lab/agentmon/hooks/gemini/hooks.json). Install the handler from that directory so the `agentmon-gemini-handler` binary is available, then point Gemini CLI at the sample hook config and set `AGENTMON_INGEST_URL` to your ingest gateway.
|
||||||
|
|
||||||
|
## Hermes Hook
|
||||||
|
|
||||||
|
The `hooks/hermes/` directory contains a TypeScript handler for Hermes Agent shell-hook telemetry. The current integration maps Hermes hook events into agentmon's session/run/span model:
|
||||||
|
|
||||||
|
- `on_session_start` maps to `session.start`
|
||||||
|
- `pre_llm_call` maps to `run.start`
|
||||||
|
- `post_llm_call` maps to `run.end`
|
||||||
|
- `pre_tool_call` maps to `span.start`
|
||||||
|
- `post_tool_call` maps to `span.end`
|
||||||
|
- `post_api_request` maps usage payloads to `metric.snapshot`
|
||||||
|
- `on_session_finalize` maps to `session.end`
|
||||||
|
|
||||||
|
Sample Hermes hook configuration lives in [hooks/hermes/hooks.yaml](/home/will/lab/agentmon/hooks/hermes/hooks.yaml). Install the handler from that directory so the `agentmon-hermes-handler` binary is available, then merge the sample `hooks:` block into `~/.hermes/config.yaml` and set `AGENTMON_INGEST_URL` to your ingest gateway.
|
||||||
|
|
||||||
## Go SDK
|
## Go SDK
|
||||||
|
|
||||||
Emit events from Go applications:
|
Emit events from Go applications:
|
||||||
|
|||||||
@@ -207,6 +207,15 @@ func main() {
|
|||||||
if nextCursor != nil {
|
if nextCursor != nil {
|
||||||
resp["next_cursor"] = nextCursor.Format(time.RFC3339Nano)
|
resp["next_cursor"] = nextCursor.Format(time.RFC3339Nano)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Include total count on the first page (no cursor) so the UI can show "X of Y"
|
||||||
|
if f.Cursor == nil {
|
||||||
|
total, err := db.CountSessions(r.Context(), f)
|
||||||
|
if err == nil {
|
||||||
|
resp["total"] = total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
httpx.WriteJSON(w, http.StatusOK, resp)
|
httpx.WriteJSON(w, http.StatusOK, resp)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func (d *DB) GetSessionWithRuns(ctx context.Context, sessionID string) (*Session
|
|||||||
session_id,
|
session_id,
|
||||||
MIN(ts) as started_at,
|
MIN(ts) as started_at,
|
||||||
MAX(CASE WHEN type = 'session.end' THEN ts END) as ended_at,
|
MAX(CASE WHEN type = 'session.end' THEN ts END) as ended_at,
|
||||||
MAX(source_framework) as framework,
|
COALESCE((ARRAY_AGG(source_framework ORDER BY CASE WHEN type = 'session.start' THEN 0 ELSE 1 END, ts) FILTER (WHERE source_framework IS NOT NULL))[1], 'unknown') as framework,
|
||||||
MAX(payload->'event'->'source'->>'host') as host
|
MAX(payload->'event'->'source'->>'host') as host
|
||||||
FROM events
|
FROM events
|
||||||
WHERE session_id = $1
|
WHERE session_id = $1
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func (d *DB) ListSessions(ctx context.Context, f SessionsFilter) ([]SessionRow,
|
|||||||
session_id,
|
session_id,
|
||||||
MIN(ts) as started_at,
|
MIN(ts) as started_at,
|
||||||
MAX(CASE WHEN type = 'session.end' THEN ts END) as ended_at,
|
MAX(CASE WHEN type = 'session.end' THEN ts END) as ended_at,
|
||||||
MAX(source_framework) as framework,
|
COALESCE((ARRAY_AGG(source_framework ORDER BY CASE WHEN type = 'session.start' THEN 0 ELSE 1 END, ts) FILTER (WHERE source_framework IS NOT NULL))[1], 'unknown') as framework,
|
||||||
MAX(client_id) as client_id,
|
MAX(client_id) as client_id,
|
||||||
MAX(payload->'event'->'source'->>'host') as host,
|
MAX(payload->'event'->'source'->>'host') as host,
|
||||||
COUNT(DISTINCT run_id) as run_count
|
COUNT(DISTINCT run_id) as run_count
|
||||||
@@ -136,3 +136,59 @@ func (d *DB) ListSessions(ctx context.Context, f SessionsFilter) ([]SessionRow,
|
|||||||
|
|
||||||
return out, nextCursor, rows.Err()
|
return out, nextCursor, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CountSessions returns the total number of sessions matching the filter (without limit or cursor).
|
||||||
|
func (d *DB) CountSessions(ctx context.Context, f SessionsFilter) (int, error) {
|
||||||
|
var innerConditions []string
|
||||||
|
var outerConditions []string
|
||||||
|
var args []any
|
||||||
|
argN := 1
|
||||||
|
|
||||||
|
if f.From == nil {
|
||||||
|
t := time.Now().Add(-24 * time.Hour)
|
||||||
|
f.From = &t
|
||||||
|
}
|
||||||
|
innerConditions = append(innerConditions, fmt.Sprintf("ts >= $%d", argN))
|
||||||
|
args = append(args, *f.From)
|
||||||
|
argN++
|
||||||
|
|
||||||
|
if f.To != nil {
|
||||||
|
innerConditions = append(innerConditions, fmt.Sprintf("ts <= $%d", argN))
|
||||||
|
args = append(args, *f.To)
|
||||||
|
argN++
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Framework != "" {
|
||||||
|
innerConditions = append(innerConditions, fmt.Sprintf("source_framework = $%d", argN))
|
||||||
|
args = append(args, f.Framework)
|
||||||
|
argN++
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Host != "" {
|
||||||
|
outerConditions = append(outerConditions, fmt.Sprintf("host = $%d", argN))
|
||||||
|
args = append(args, f.Host)
|
||||||
|
argN++
|
||||||
|
}
|
||||||
|
|
||||||
|
innerWhere := strings.Join(innerConditions, " AND ")
|
||||||
|
outerWhere := ""
|
||||||
|
if len(outerConditions) > 0 {
|
||||||
|
outerWhere = "WHERE " + strings.Join(outerConditions, " AND ")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
WITH session_groups AS (
|
||||||
|
SELECT
|
||||||
|
session_id,
|
||||||
|
MAX(payload->'event'->'source'->>'host') as host
|
||||||
|
FROM events
|
||||||
|
WHERE session_id IS NOT NULL AND %s
|
||||||
|
GROUP BY session_id
|
||||||
|
)
|
||||||
|
SELECT COUNT(*) FROM session_groups %s
|
||||||
|
`, innerWhere, outerWhere)
|
||||||
|
|
||||||
|
var count int
|
||||||
|
err := d.sql.QueryRowContext(ctx, query, args...).Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func (d *DB) GetSummary(ctx context.Context) (*Summary, error) {
|
|||||||
WITH session_groups AS (
|
WITH session_groups AS (
|
||||||
SELECT
|
SELECT
|
||||||
session_id,
|
session_id,
|
||||||
COALESCE(MAX(source_framework), 'unknown') AS framework,
|
COALESCE((ARRAY_AGG(source_framework ORDER BY CASE WHEN type = 'session.start' THEN 0 ELSE 1 END, ts) FILTER (WHERE source_framework IS NOT NULL))[1], 'unknown') AS framework,
|
||||||
MAX(ts) AS last_event_at,
|
MAX(ts) AS last_event_at,
|
||||||
BOOL_OR(type = 'session.start') AS has_start,
|
BOOL_OR(type = 'session.start') AS has_start,
|
||||||
BOOL_OR(type = 'session.end') AS has_end
|
BOOL_OR(type = 'session.end') AS has_end
|
||||||
|
|||||||
Reference in New Issue
Block a user