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
10 changes: 7 additions & 3 deletions docs/ai-coder/ai-gateway/audit.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Auditing AI Sessions

Check warning on line 1 in docs/ai-coder/ai-gateway/audit.md

View workflow job for this annotation

GitHub Actions / lint-docs

Coder.GerundHeading

Heading starts with an -ing word ('Auditing'); prefer the imperative ('Install') or the noun ('Installation'). See capitalization-and-punctuation.md#no-gerund-leading-headings.

> [!NOTE]
> AI Gateway is part of [AI Governance](../ai-governance.md), which is
Expand Down Expand Up @@ -42,13 +42,17 @@
feature. Reasoning data gives auditors insight into **why** a tool was called,
not just what was called.

## Navigating the UI

Check warning on line 45 in docs/ai-coder/ai-gateway/audit.md

View workflow job for this annotation

GitHub Actions / lint-docs

Coder.GerundHeading

Heading starts with an -ing word ('Navigating'); prefer the imperative ('Install') or the noun ('Installation'). See capitalization-and-punctuation.md#no-gerund-leading-headings.

### Sessions list

The sessions page (`http://<deployment-url>/ai-gateway/sessions`) lists all sessions in
reverse-chronological order. Each row shows the last prompt, initiator, provider,
client, token usage, network requests, thread count, and timestamp.
The sessions page (`http://<deployment-url>/ai-gateway/sessions`) lists sessions in
reverse-chronological order. By default it shows sessions with activity in the
last 24 hours. Use the time range filter in the filter bar to widen or narrow
the window, for example to see older sessions.

Each row shows the last prompt, initiator, provider, client, token usage,
network requests, thread count, and timestamp.

The **Network Requests** column reports the total and blocked
[Agent Firewall](../agent-firewall/index.md) requests for the session. It shows
Expand Down Expand Up @@ -82,7 +86,7 @@

![Session detail](../../images/aibridge/session_detail.png)

## Conducting a forensic audit

Check warning on line 89 in docs/ai-coder/ai-gateway/audit.md

View workflow job for this annotation

GitHub Actions / lint-docs

Coder.GerundHeading

Heading starts with an -ing word ('Conducting'); prefer the imperative ('Install') or the noun ('Installation'). See capitalization-and-punctuation.md#no-gerund-leading-headings.

When investigating an incident (policy violation, destructive action, etc.):

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import type { ComponentProps } from "react";
import { fn } from "storybook/test";
import type { TimeRange } from "#/components/DateTimeRangeFilter/timeRange";
import {
getDefaultFilterProps,
MockMenu,
Expand All @@ -8,21 +10,38 @@ import { ListSessionsFilter } from "./ListSessionsFilter";

type FilterProps = ComponentProps<typeof ListSessionsFilter>;

const defaultFilterProps = getDefaultFilterProps<
Pick<FilterProps, "filter" | "menus">
>({
query: "",
values: {
username: undefined,
provider: undefined,
},
menus: {
user: MockMenu,
provider: MockMenu,
client: MockMenu,
model: MockMenu,
},
});
type FilterAndMenus = Pick<FilterProps, "filter" | "menus">;

const timeRange: TimeRange = {
startedAfter: new Date("2026-08-12T15:00:00Z"),
startedBefore: new Date("2026-08-13T15:00:00Z"),
};

const timeRangeProps: Pick<
FilterProps,
"timeRange" | "defaultTimeRange" | "onTimeRangeChange"
> = {
timeRange,
defaultTimeRange: timeRange,
onTimeRangeChange: fn(),
};

const defaultFilterProps = {
...getDefaultFilterProps<FilterAndMenus>({
query: "",
values: {
username: undefined,
provider: undefined,
},
menus: {
user: MockMenu,
provider: MockMenu,
client: MockMenu,
model: MockMenu,
},
}),
...timeRangeProps,
};

const meta: Meta<typeof ListSessionsFilter> = {
title: "pages/AIBridgePage/ListSessionsFilter",
Expand All @@ -40,7 +59,7 @@ export const Default: Story = {

export const WithQuery: Story = {
args: {
...getDefaultFilterProps<Pick<FilterProps, "filter" | "menus">>({
...getDefaultFilterProps<FilterAndMenus>({
query: "initiator:me",
values: {
username: "me",
Expand All @@ -54,6 +73,17 @@ export const WithQuery: Story = {
},
used: true,
}),
...timeRangeProps,
},
};

export const ExplicitTimeRange: Story = {
args: {
...defaultFilterProps,
timeRange: {
startedAfter: new Date("2026-08-01T09:30:00"),
startedBefore: new Date("2026-08-02T17:45:00"),
Comment thread
jeremyruppel marked this conversation as resolved.
},
},
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { FC } from "react";
import { DateTimeRangeFilter } from "#/components/DateTimeRangeFilter/DateTimeRangeFilter";
import type { TimeRange } from "#/components/DateTimeRangeFilter/timeRange";
import {
Filter,
MenuSkeleton,
Expand All @@ -12,6 +14,10 @@ import {
type ProviderFilterMenu,
} from "../filters/ProviderFilter";

// Narrower than the SelectFilter default so the search input keeps most
// of the row on wide viewports.
const FILTER_WIDTH = 150;

interface ListSessionsFilterProps {
filter: ReturnType<typeof useFilter>;
error?: unknown;
Expand All @@ -21,12 +27,18 @@ interface ListSessionsFilterProps {
client: ClientFilterMenu;
model: ModelFilterMenu;
};
timeRange: TimeRange;
defaultTimeRange: TimeRange;
onTimeRangeChange: (range: TimeRange) => void;
}

export const ListSessionsFilter: FC<ListSessionsFilterProps> = ({
filter,
error,
menus,
timeRange,
defaultTimeRange,
onTimeRangeChange,
}) => {
return (
<Filter
Expand All @@ -46,10 +58,20 @@ export const ListSessionsFilter: FC<ListSessionsFilterProps> = ({
error={error}
options={
<>
<UserMenu menu={menus.user} placeholder="All users" />
<ProviderFilter menu={menus.provider} />
<ClientFilter menu={menus.client} />
<ModelFilter menu={menus.model} />
<DateTimeRangeFilter
value={timeRange}
defaultValue={defaultTimeRange}
onChange={onTimeRangeChange}
width={FILTER_WIDTH}
/>
<UserMenu
menu={menus.user}
placeholder="All users"
width={FILTER_WIDTH}
/>
<ProviderFilter menu={menus.provider} width={FILTER_WIDTH} />
<ClientFilter menu={menus.client} width={FILTER_WIDTH} />
<ModelFilter menu={menus.model} width={FILTER_WIDTH} />
</>
}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { FC } from "react";
import { useState } from "react";
import { useNavigate, useSearchParams } from "react-router";
import { paginatedSessions } from "#/api/queries/aiBridge";
import { useFilter } from "#/components/Filter/Filter";
import { useFilter, useFilterParamsKey } from "#/components/Filter/Filter";
import { useUserFilterMenu } from "#/components/Filter/UserFilter";
import { useAuthenticated } from "#/hooks/useAuthenticated";
import { usePaginatedQuery } from "#/hooks/usePaginatedQuery";
Expand All @@ -13,6 +14,12 @@ import { useModelFilterMenu } from "../filters/ModelFilter";
import { useProviderFilterMenu } from "../filters/ProviderFilter";
import { getAIBridgePermissions } from "../getAIBridgePermissions";
import { ListSessionsPageView } from "./ListSessionsPageView";
import {
defaultTimeRange,
parseTimeRange,
queryWithTimeRange,
withDefaultTimeRange,
} from "./timeRange";

const AISessionListPage: FC = () => {
const { permissions } = useAuthenticated();
Expand All @@ -26,9 +33,21 @@ const AISessionListPage: FC = () => {

const canViewSessions = isEntitled && hasPermission;

// The default time range lives in memory, not the URL, so a shared link
// resolves relative to the viewer's current time. It is fixed per mount
// so query cache keys stay stable.
const [defaultRange] = useState(() => defaultTimeRange(new Date()));

const [searchParams, setSearchParams] = useSearchParams();
const sessionsQuery = usePaginatedQuery({
...paginatedSessions(searchParams),
// Merge the default range into every fetch (including prefetches) so
// the unfiltered sessions query never scans the entire table.
queryPayload: () =>
withDefaultTimeRange(
searchParams.get(useFilterParamsKey) ?? "",
defaultRange,
),
enabled: canViewSessions,
});

Expand All @@ -38,6 +57,8 @@ const AISessionListPage: FC = () => {
onUpdate: sessionsQuery.goToFirstPage,
});

const explicitTimeRange = parseTimeRange(filter.values);
const timeRange = explicitTimeRange ?? defaultRange;
Comment thread
johnstcn marked this conversation as resolved.
const userMenu = useUserFilterMenu({
value: filter.values.initiator,
onChange: (option) =>
Expand Down Expand Up @@ -97,6 +118,10 @@ const AISessionListPage: FC = () => {
client: clientMenu,
model: modelMenu,
},
timeRange,
defaultTimeRange: defaultRange,
onTimeRangeChange: (range) =>
filter.update(queryWithTimeRange(filter.values, range)),
}}
/>
</RequirePermission>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import type { ComponentProps } from "react";
import { fn } from "storybook/test";
import type { TimeRange } from "#/components/DateTimeRangeFilter/timeRange";
import {
getDefaultFilterProps,
MockMenu,
Expand All @@ -14,19 +15,29 @@ import { ListSessionsPageView } from "./ListSessionsPageView";

type FilterProps = ComponentProps<typeof ListSessionsPageView>["filterProps"];

const defaultFilterProps = getDefaultFilterProps<FilterProps>({
query: "owner:me",
values: {
username: undefined,
provider: undefined,
},
menus: {
user: MockMenu,
provider: MockMenu,
client: MockMenu,
model: MockMenu,
},
});
const timeRange: TimeRange = {
startedAfter: new Date("2026-08-12T15:00:00Z"),
startedBefore: new Date("2026-08-13T15:00:00Z"),
};

const defaultFilterProps: FilterProps = {
...getDefaultFilterProps<FilterProps>({
query: "owner:me",
values: {
username: undefined,
provider: undefined,
},
menus: {
user: MockMenu,
provider: MockMenu,
client: MockMenu,
model: MockMenu,
},
}),
timeRange,
defaultTimeRange: timeRange,
onTimeRangeChange: fn(),
};

const meta: Meta<typeof ListSessionsPageView> = {
title: "pages/AIBridgePage/ListSessionsPageView",
Expand Down
Loading
Loading