feat(traffic-inspector): live (in-flight) request filter (Gap 5) (#4130)

Integrated into release/v3.8.29
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-18 00:25:52 -03:00
committed by GitHub
parent 91183dc27e
commit 1faf72bbda
7 changed files with 171 additions and 20 deletions

View File

@@ -19,8 +19,16 @@ export function TrafficInspectorPageClient() {
const [containerHeight, setContainerHeight] = useState(600);
const listContainerRef = useRef<HTMLDivElement | null>(null);
const [selectedRequest, setSelectedRequest] = useState<InterceptedRequest | null>(null);
const { filters, setProfile, setHost, setAgent, setStatus, setSessionId, setSameContext } =
useTrafficFilters();
const {
filters,
setProfile,
setHost,
setAgent,
setStatus,
setSessionId,
setSameContext,
toggleLive,
} = useTrafficFilters();
const [{ listWidth, collapsed }, { startDrag, toggleCollapse }] = useResizablePanels();
const [streamState, streamActions] = useTrafficStream(filters);
const recorder = useSessionRecorder();
@@ -108,6 +116,8 @@ export function TrafficInspectorPageClient() {
onHostChange={setHost}
onAgentChange={setAgent}
onStatusChange={setStatus}
liveOnly={filters.liveOnly ?? false}
onToggleLive={toggleLive}
paused={streamState.paused}
onPause={streamActions.pause}
onResume={streamActions.resume}

View File

@@ -19,6 +19,8 @@ interface TopBarControlsProps {
onHostChange: (h: string | undefined) => void;
onAgentChange: (a: AgentId | undefined) => void;
onStatusChange: (s: ListFilters["status"]) => void;
liveOnly: boolean;
onToggleLive: () => void;
paused: boolean;
onPause: () => void;
onResume: () => void;
@@ -46,6 +48,8 @@ export function TopBarControls({
onHostChange,
onAgentChange,
onStatusChange,
liveOnly,
onToggleLive,
paused,
onPause,
onResume,
@@ -125,6 +129,25 @@ export function TopBarControls({
<option value="error">error</option>
</select>
{/* Live (in-flight) toggle — Gap 5 */}
<button
type="button"
onClick={onToggleLive}
aria-pressed={liveOnly}
title={t("liveOnly")}
className={cn(
"inline-flex items-center gap-1 rounded border px-2 py-1 text-xs focus-ring",
liveOnly
? "border-green-500/50 bg-green-500/15 text-green-500"
: "border-border text-text-muted hover:text-text-main"
)}
>
<span className="material-symbols-outlined text-[14px]" aria-hidden="true">
sensors
</span>
{t("liveOnly")}
</button>
{/* Action buttons */}
<button
type="button"

View File

@@ -5,6 +5,8 @@ import type { ListFilters } from "@/mitm/inspector/types";
export interface FiltersState extends ListFilters {
sameContextKey?: string;
/** Show only in-flight (open) requests — Gap 5 "Live" filter. */
liveOnly?: boolean;
}
export function useTrafficFilters() {
@@ -38,6 +40,10 @@ export function useTrafficFilters() {
setFilters((prev) => ({ ...prev, sameContextKey: contextKey }));
}, []);
const toggleLive = useCallback(() => {
setFilters((prev) => ({ ...prev, liveOnly: !prev.liveOnly }));
}, []);
const reset = useCallback(() => {
setFilters({ profile: "llm" });
}, []);
@@ -51,6 +57,7 @@ export function useTrafficFilters() {
setSource,
setSessionId,
setSameContext,
toggleLive,
reset,
};
}

View File

@@ -2,6 +2,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import type { InterceptedRequest, ListFilters, WsEvent } from "@/mitm/inspector/types";
import { matchesTrafficFilter } from "@/lib/inspector/matchesTrafficFilter";
import type { FiltersState } from "./useTrafficFilters";
const WS_PATH = "/api/tools/traffic-inspector/ws";
@@ -47,24 +48,7 @@ export function useTrafficStream(
});
const applyFilter = useCallback((req: InterceptedRequest): boolean => {
const f = filtersRef.current as FiltersState;
if (f.profile === "llm" && req.detectedKind !== "llm") return false;
if (f.profile === "custom" && req.source !== "custom-host") return false;
if (f.host && !req.host.includes(f.host)) return false;
if (f.agent && req.agent !== f.agent) return false;
if (f.source && req.source !== f.source) return false;
if (f.sessionId && req.sessionId !== f.sessionId) return false;
if (f.sameContextKey && req.contextKey !== f.sameContextKey) return false;
if (f.status) {
const s = req.status;
if (typeof s === "number") {
const cat = `${Math.floor(s / 100)}xx`;
if (cat !== f.status) return false;
} else if (f.status === "error" && s !== "error") {
return false;
}
}
return true;
return matchesTrafficFilter(req, filtersRef.current as FiltersState);
}, []);
useEffect(() => {

View File

@@ -8578,6 +8578,7 @@
"httpProxyTitle": "HTTP Proxy Snippet — port {port}",
"notRecording": "Not recording",
"anyStatus": "Any status",
"liveOnly": "Live",
"viewingRecordedSession": "Viewing recorded session",
"backToLive": "Back to live",
"untitledSession": "Untitled session",

View File

@@ -0,0 +1,37 @@
/**
* Pure request-list filter for the Traffic Inspector.
*
* Extracted from the inline `applyFilter` closure inside the useTrafficStream
* hook so the filtering rules are unit-testable. Behaviour is identical to the
* previous inline logic, plus the new `liveOnly` toggle (Gap 5) that keeps only
* in-flight requests — letting the user watch open connections in real time.
*/
import type { InterceptedRequest, ListFilters } from "@/mitm/inspector/types";
export type TrafficFilters = ListFilters & {
/** Client-only: restrict the list to the same system-prompt context. */
sameContextKey?: string;
/** Client-only: show only in-flight (open) requests (Gap 5). */
liveOnly?: boolean;
};
export function matchesTrafficFilter(req: InterceptedRequest, f: TrafficFilters): boolean {
if (f.profile === "llm" && req.detectedKind !== "llm") return false;
if (f.profile === "custom" && req.source !== "custom-host") return false;
if (f.host && !req.host.includes(f.host)) return false;
if (f.agent && req.agent !== f.agent) return false;
if (f.source && req.source !== f.source) return false;
if (f.sessionId && req.sessionId !== f.sessionId) return false;
if (f.sameContextKey && req.contextKey !== f.sameContextKey) return false;
if (f.liveOnly && req.status !== "in-flight") return false;
if (f.status) {
const s = req.status;
if (typeof s === "number") {
const cat = `${Math.floor(s / 100)}xx`;
if (cat !== f.status) return false;
} else if (f.status === "error" && s !== "error") {
return false;
}
}
return true;
}

View File

@@ -0,0 +1,89 @@
/**
* Gap 5 — "Live" (in-flight) filter for the Traffic Inspector.
*
* The request-list filter logic used to live inline inside the useTrafficStream
* hook (untested). It is extracted here into a pure `matchesTrafficFilter` so the
* new `liveOnly` toggle — and, for the first time, the pre-existing profile/host/
* agent/status rules — are unit-tested. These tests pin BOTH the new behavior and
* parity with the old inline logic.
*/
import test from "node:test";
import assert from "node:assert/strict";
import type { InterceptedRequest } from "../../src/mitm/inspector/types.ts";
const { matchesTrafficFilter } = await import("../../src/lib/inspector/matchesTrafficFilter.ts");
function mkReq(partial: Partial<InterceptedRequest>): InterceptedRequest {
return {
id: "00000000-0000-0000-0000-000000000000",
source: "agent-bridge",
timestamp: "2026-06-17T00:00:00.000Z",
method: "POST",
host: "api.openai.com",
path: "/v1/chat/completions",
requestHeaders: {},
requestBody: null,
requestSize: 0,
responseHeaders: {},
responseBody: null,
responseSize: 0,
status: 200,
detectedKind: "llm",
...partial,
};
}
test("liveOnly keeps in-flight requests and drops finished ones", () => {
assert.equal(matchesTrafficFilter(mkReq({ status: "in-flight" }), { liveOnly: true }), true);
assert.equal(matchesTrafficFilter(mkReq({ status: 200 }), { liveOnly: true }), false);
assert.equal(matchesTrafficFilter(mkReq({ status: "error" }), { liveOnly: true }), false);
});
test("liveOnly off (or unset) does not filter by in-flight", () => {
assert.equal(matchesTrafficFilter(mkReq({ status: 200 }), {}), true);
assert.equal(matchesTrafficFilter(mkReq({ status: 200 }), { liveOnly: false }), true);
assert.equal(matchesTrafficFilter(mkReq({ status: "in-flight" }), {}), true);
});
test("liveOnly composes with other filters (host must still match)", () => {
const req = mkReq({ status: "in-flight", host: "api.anthropic.com" });
assert.equal(matchesTrafficFilter(req, { liveOnly: true, host: "openai" }), false);
assert.equal(matchesTrafficFilter(req, { liveOnly: true, host: "anthropic" }), true);
});
// ── Parity with the previous inline applyFilter logic ────────────────────────
test("profile=llm drops non-llm requests", () => {
assert.equal(matchesTrafficFilter(mkReq({ detectedKind: "app" }), { profile: "llm" }), false);
assert.equal(matchesTrafficFilter(mkReq({ detectedKind: "llm" }), { profile: "llm" }), true);
});
test("profile=custom keeps only custom-host source", () => {
assert.equal(matchesTrafficFilter(mkReq({ source: "agent-bridge" }), { profile: "custom" }), false);
assert.equal(matchesTrafficFilter(mkReq({ source: "custom-host" }), { profile: "custom" }), true);
});
test("host filter is a substring match", () => {
assert.equal(matchesTrafficFilter(mkReq({ host: "api.openai.com" }), { host: "openai" }), true);
assert.equal(matchesTrafficFilter(mkReq({ host: "api.openai.com" }), { host: "google" }), false);
});
test("agent, source, sessionId and sameContextKey filters", () => {
assert.equal(matchesTrafficFilter(mkReq({ agent: "claude-code" }), { agent: "codex" }), false);
assert.equal(matchesTrafficFilter(mkReq({ source: "http-proxy" }), { source: "system-proxy" }), false);
assert.equal(matchesTrafficFilter(mkReq({ sessionId: "a" }), { sessionId: "b" }), false);
assert.equal(matchesTrafficFilter(mkReq({ contextKey: "abc" }), { sameContextKey: "xyz" }), false);
assert.equal(matchesTrafficFilter(mkReq({ contextKey: "abc" }), { sameContextKey: "abc" }), true);
});
test("status category filter (2xx/4xx/5xx/error)", () => {
assert.equal(matchesTrafficFilter(mkReq({ status: 200 }), { status: "2xx" }), true);
assert.equal(matchesTrafficFilter(mkReq({ status: 404 }), { status: "2xx" }), false);
assert.equal(matchesTrafficFilter(mkReq({ status: 503 }), { status: "5xx" }), true);
assert.equal(matchesTrafficFilter(mkReq({ status: "error" }), { status: "error" }), true);
assert.equal(matchesTrafficFilter(mkReq({ status: 200 }), { status: "error" }), false);
});
test("an empty filter set matches everything", () => {
assert.equal(matchesTrafficFilter(mkReq({}), {}), true);
});