mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add network calls list to AI session threads API (#27425)
The AI session threads API returned only a network call *summary* (total/blocked counts + top domains). This adds the per-call list so the session detail can render individual Agent Firewall network calls. `ListAIBridgeSessionNetworkCalls` reuses the same sequence-number windowing as the existing summary and includes all protocols. The list is exposed as `network_call_logs` on the threads response and is capped server-side at 100 rows. The summary (`network_calls.total`/`blocked`) remains authoritative for whole-session totals: the list length and its blocked count equal the summary only when a session has at most 100 calls, and are truncated beyond that. ### PR map (merge strictly bottom-up) This change is a 4-PR stack. Each PR depends on all the ones below it, so merge in this exact order: 1. #27417 — backend network summary 2. #27418 — frontend summary rows 3. #27425 — backend per-call list `network_call_logs` 4. #27426 — frontend network-calls panel Refs AIGOV-464 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fba9f0d485
commit
8886a5749a
Generated
+79
@@ -2024,6 +2024,85 @@ func (q *sqlQuerier) ListAIBridgeModels(ctx context.Context, arg ListAIBridgeMod
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listAIBridgeSessionNetworkCalls = `-- name: ListAIBridgeSessionNetworkCalls :many
|
||||
SELECT bl.id, bl.session_id, bl.sequence_number, bl.captured_at, bl.created_at, bl.proto, bl.method, bl.detail, bl.matched_rule, bl.owner_id
|
||||
FROM aibridge_interceptions afi
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COALESCE(MIN(nxt.agent_firewall_sequence_number), 2147483647) AS next_seq
|
||||
FROM aibridge_interceptions nxt
|
||||
WHERE nxt.agent_firewall_session_id = afi.agent_firewall_session_id
|
||||
AND nxt.agent_firewall_sequence_number > afi.agent_firewall_sequence_number
|
||||
) w ON true
|
||||
JOIN boundary_logs bl
|
||||
ON bl.session_id = afi.agent_firewall_session_id
|
||||
AND bl.sequence_number > afi.agent_firewall_sequence_number
|
||||
AND bl.sequence_number < w.next_seq
|
||||
WHERE afi.session_id = $1::text
|
||||
AND afi.ended_at IS NOT NULL
|
||||
AND afi.agent_firewall_session_id IS NOT NULL
|
||||
AND afi.agent_firewall_sequence_number IS NOT NULL
|
||||
ORDER BY bl.created_at ASC, bl.sequence_number ASC, bl.id ASC
|
||||
LIMIT COALESCE(NULLIF($2::integer, 0), 1000)
|
||||
`
|
||||
|
||||
type ListAIBridgeSessionNetworkCallsParams struct {
|
||||
SessionID string `db:"session_id" json:"session_id"`
|
||||
Limit int32 `db:"limit_" json:"limit_"`
|
||||
}
|
||||
|
||||
// Returns the individual Agent Firewall network calls made during an AI
|
||||
// session, ordered chronologically. All protocols are included, unlike
|
||||
// GetAIBridgeSessionTopDomains which considers only HTTP egress, so the list
|
||||
// covers the same events the network_calls summary in ListAIBridgeSessions
|
||||
// counts. The list is capped at @limit_ rows, so its length equals the summary
|
||||
// total only for sessions at or below the cap. The summary stays authoritative
|
||||
// for whole-session totals.
|
||||
//
|
||||
// Windowing mirrors that summary and GetAIBridgeSessionTopDomains: each
|
||||
// interception's boundary logs fall in the open interval (this seq, next
|
||||
// interception's seq) within the same firewall session. The exclusive lower
|
||||
// bound drops the interception's own LLM-provider call. next_seq considers all
|
||||
// interceptions in the firewall session so windows never bleed across AI
|
||||
// sessions that share one firewall session, and falls back to the maximum
|
||||
// sequence_number for the last interception so the window stays an
|
||||
// index-satisfiable range.
|
||||
// created_at leads because a session can span several firewall sessions, whose
|
||||
// sequence numbers are independent streams. id breaks remaining ties so the row
|
||||
// that lands on the limit boundary is stable across identical requests.
|
||||
func (q *sqlQuerier) ListAIBridgeSessionNetworkCalls(ctx context.Context, arg ListAIBridgeSessionNetworkCallsParams) ([]BoundaryLog, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listAIBridgeSessionNetworkCalls, arg.SessionID, arg.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []BoundaryLog
|
||||
for rows.Next() {
|
||||
var i BoundaryLog
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.SessionID,
|
||||
&i.SequenceNumber,
|
||||
&i.CapturedAt,
|
||||
&i.CreatedAt,
|
||||
&i.Proto,
|
||||
&i.Method,
|
||||
&i.Detail,
|
||||
&i.MatchedRule,
|
||||
&i.OwnerID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listAIBridgeSessionThreads = `-- name: ListAIBridgeSessionThreads :many
|
||||
WITH paginated_threads AS (
|
||||
SELECT
|
||||
|
||||
Reference in New Issue
Block a user