Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions coderd/database/db2sdk/db2sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -1038,11 +1038,12 @@ func AIBridgeSession(row database.ListAIBridgeSessionsRow) codersdk.AIBridgeSess
Name: row.UserName,
AvatarURL: row.UserAvatarUrl,
}),
Providers: row.Providers,
Models: row.Models,
Metadata: jsonOrEmptyMap(pqtype.NullRawMessage{RawMessage: row.Metadata, Valid: len(row.Metadata) > 0}),
StartedAt: row.StartedAt,
Threads: row.Threads,
Providers: row.Providers,
Models: row.Models,
Metadata: jsonOrEmptyMap(pqtype.NullRawMessage{RawMessage: row.Metadata, Valid: len(row.Metadata) > 0}),
StartedAt: row.StartedAt,
Threads: row.Threads,
LastActiveAt: row.LastActiveAt,
TokenUsageSummary: codersdk.AIBridgeSessionTokenUsageSummary{
InputTokens: row.InputTokens,
OutputTokens: row.OutputTokens,
Expand Down
1 change: 1 addition & 0 deletions coderd/database/modelqueries.go
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,7 @@ func (q *sqlQuerier) ListAuthorizedAIBridgeSessions(ctx context.Context, arg Lis
&i.CacheReadInputTokens,
&i.CacheWriteInputTokens,
&i.LastPrompt,
&i.LastActiveAt,
); err != nil {
return nil, err
}
Expand Down
55 changes: 39 additions & 16 deletions coderd/database/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 37 additions & 16 deletions coderd/database/queries/aibridge.sql
Original file line number Diff line number Diff line change
Expand Up @@ -446,22 +446,43 @@ WHERE
-- single GROUP BY scan, then do expensive lateral joins (tokens, prompts,
-- first-interception metadata) only for the ~page-size result set.
WITH cursor_pos AS (
-- Resolve the cursor's started_at once, outside the HAVING clause,
-- so the planner cannot accidentally re-evaluate it per group.
SELECT MIN(aibridge_interceptions.started_at) AS started_at
FROM aibridge_interceptions
WHERE aibridge_interceptions.session_id = @after_session_id AND aibridge_interceptions.ended_at IS NOT NULL
-- Resolve the cursor's last_active_at once, outside the HAVING clause,
-- so the planner cannot accidentally re-evaluate it per group. Direct
-- LEFT JOIN is safe here since we only use MAX/MIN aggregates (no COUNT
-- affected by fan-out from multiple prompts per interception).
-- COALESCE falls back to MIN(ai.started_at) so the cursor value is
-- never NULL, which would silently drop rows from the HAVING comparison.
SELECT COALESCE(MAX(up.created_at), MIN(ai.started_at)) AS last_active_at
FROM aibridge_interceptions ai
LEFT JOIN aibridge_user_prompts up ON up.interception_id = ai.id
WHERE ai.session_id = @after_session_id AND ai.ended_at IS NOT NULL
),
session_page AS (
-- Paginate at the session level first; only cheap aggregates here.
-- A lateral correlated subquery for prompts keeps the join one-to-one
-- with aibridge_interceptions so COUNT(*) for thread tallies is not
-- inflated. LIMIT 1 combined with the (interception_id, created_at DESC)
-- index makes this an index-only lookup per interception row rather than
-- a full-table-scan GROUP BY over all prompts.
-- last_active_at is the latest prompt timestamp, falling back to
-- MIN(started_at) for sessions with no prompts. The COALESCE ensures
-- it is never NULL so the HAVING row-value cursor comparison is safe.
SELECT
ai.session_id,
ai.initiator_id,
MIN(ai.started_at) AS started_at,
MAX(ai.ended_at) AS ended_at,
COUNT(*) FILTER (WHERE ai.thread_root_id IS NULL) AS threads
COUNT(*) FILTER (WHERE ai.thread_root_id IS NULL) AS threads,
COALESCE(MAX(latest_prompt.latest_prompt_at), MIN(ai.started_at))::timestamptz AS last_active_at
FROM
aibridge_interceptions ai
LEFT JOIN LATERAL (
SELECT created_at AS latest_prompt_at
FROM aibridge_user_prompts
WHERE interception_id = ai.id
ORDER BY created_at DESC
LIMIT 1
) latest_prompt ON true
WHERE
-- Remove inflight interceptions (ones which lack an ended_at value).
ai.ended_at IS NOT NULL
Expand Down Expand Up @@ -504,22 +525,21 @@ session_page AS (
GROUP BY
ai.session_id, ai.initiator_id
HAVING
-- Cursor pagination: uses a composite (started_at, session_id)
-- cursor to support keyset pagination. The less-than comparison
-- matches the DESC sort order so rows after the cursor come
-- later in results. The cursor value comes from cursor_pos to
-- guarantee single evaluation.
-- Cursor pagination: uses a composite (last_active_at, session_id) cursor to
-- support keyset pagination. The less-than comparison matches the DESC
-- sort order so rows after the cursor come later in results. The cursor
-- value comes from cursor_pos to guarantee single evaluation.
CASE
WHEN @after_session_id::text != '' THEN (
(MIN(ai.started_at), ai.session_id) < (
(SELECT started_at FROM cursor_pos),
(COALESCE(MAX(latest_prompt.latest_prompt_at), MIN(ai.started_at)), ai.session_id) < (
(SELECT last_active_at FROM cursor_pos),
@after_session_id::text
)
)
ELSE true
END
ORDER BY
MIN(ai.started_at) DESC,
last_active_at DESC,
ai.session_id DESC
LIMIT COALESCE(NULLIF(@limit_::integer, 0), 100)
OFFSET @offset_
Expand All @@ -541,7 +561,8 @@ SELECT
COALESCE(st.output_tokens, 0)::bigint AS output_tokens,
COALESCE(st.cache_read_input_tokens, 0)::bigint AS cache_read_input_tokens,
COALESCE(st.cache_write_input_tokens, 0)::bigint AS cache_write_input_tokens,
COALESCE(slp.prompt, '') AS last_prompt
COALESCE(slp.prompt, '') AS last_prompt,
sp.last_active_at AS last_active_at
FROM
session_page sp
JOIN
Expand Down Expand Up @@ -578,7 +599,7 @@ LEFT JOIN LATERAL (
LIMIT 1
) slp ON true
ORDER BY
sp.started_at DESC,
sp.last_active_at DESC,
sp.session_id DESC
;

Expand Down
1 change: 1 addition & 0 deletions codersdk/aibridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ type AIBridgeSession struct {
Threads int64 `json:"threads"`
TokenUsageSummary AIBridgeSessionTokenUsageSummary `json:"token_usage_summary"`
LastPrompt *string `json:"last_prompt,omitempty"`
LastActiveAt time.Time `json:"last_active_at" format:"date-time"`
}

type AIBridgeSessionTokenUsageSummary struct {
Expand Down
1 change: 1 addition & 0 deletions docs/reference/api/aibridge.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions docs/reference/api/schemas.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading