diff --git a/open-sse/utils/progressTracker.ts b/open-sse/utils/progressTracker.ts
index aaaede2223..fda582d12d 100644
--- a/open-sse/utils/progressTracker.ts
+++ b/open-sse/utils/progressTracker.ts
@@ -90,6 +90,10 @@ export function createProgressTransform({
}
}
},
+
+ cancel() {
+ clearInterval(intervalId);
+ },
},
{ highWaterMark: 16384 },
{ highWaterMark: 16384 }
diff --git a/open-sse/utils/requestLogger.ts b/open-sse/utils/requestLogger.ts
index bdb78be2fb..560c65a1a6 100644
--- a/open-sse/utils/requestLogger.ts
+++ b/open-sse/utils/requestLogger.ts
@@ -270,7 +270,8 @@ function makeStreamChunkMethods(options: RequestLoggerOptions, captureChunks: bo
const append = (arr: string[], bytes: { value: number; truncated: boolean }, chunk: string) => {
if (!captureChunks) return;
push();
- appendBoundedChunk(arr, bytes, chunk, maxBytes, maxItems);
+ const ts = new Date().toISOString().slice(11, 23);
+ appendBoundedChunk(arr, bytes, `[${ts}] ${chunk}`, maxBytes, maxItems);
};
return {
diff --git a/open-sse/utils/sseHeartbeat.ts b/open-sse/utils/sseHeartbeat.ts
index f0ef75a26f..9a12214145 100644
--- a/open-sse/utils/sseHeartbeat.ts
+++ b/open-sse/utils/sseHeartbeat.ts
@@ -111,5 +111,9 @@ export function createSseHeartbeatTransform({
flush() {
stop();
},
+
+ cancel() {
+ stop();
+ },
});
}
diff --git a/open-sse/utils/streamPayloadCollector.ts b/open-sse/utils/streamPayloadCollector.ts
index 26246601b1..26e9f4418e 100644
--- a/open-sse/utils/streamPayloadCollector.ts
+++ b/open-sse/utils/streamPayloadCollector.ts
@@ -3,6 +3,7 @@ import { FORMATS } from "../translator/formats.ts";
type StructuredSSEEvent = {
index: number;
+ timestamp?: string;
event?: string;
data: unknown;
};
@@ -660,6 +661,7 @@ export function createStructuredSSECollector(options: CollectorOptions = {}) {
const event: StructuredSSEEvent = {
index: events.length + droppedEvents,
+ timestamp: new Date().toISOString(),
data: cloneLogPayload(payload),
};
diff --git a/src/app/api/usage/call-logs/route.ts b/src/app/api/usage/call-logs/route.ts
index 5a469cb59e..e59ab37e0b 100644
--- a/src/app/api/usage/call-logs/route.ts
+++ b/src/app/api/usage/call-logs/route.ts
@@ -63,6 +63,7 @@ export function buildCallLogListRows({
apiKeyName: null,
comboName: null,
error: null,
+ correlationId: detail.correlationId || null,
active: true,
});
}
@@ -96,6 +97,7 @@ export function buildCallLogListRows({
apiKeyName: null,
comboName: null,
error: detail.error || null,
+ correlationId: detail.correlationId || null,
active: false,
completed: true,
completedAt: completedAt ? new Date(completedAt).toISOString() : null,
@@ -104,9 +106,12 @@ export function buildCallLogListRows({
}
return [...activeEntries, ...completedEntries, ...logs].sort((a, b) => {
- const timestampDelta = rowTimestampMs(b) - rowTimestampMs(a);
- if (timestampDelta !== 0) return timestampDelta;
- return rowPriority(a) - rowPriority(b);
+ // Active requests always on top
+ const pa = rowPriority(a);
+ const pb = rowPriority(b);
+ if (pa !== pb) return pa - pb;
+ // Within same priority, newest first
+ return rowTimestampMs(b) - rowTimestampMs(a);
});
}
@@ -125,19 +130,28 @@ export async function GET(request: Request) {
if (searchParams.get("apiKey")) filter.apiKey = searchParams.get("apiKey");
if (searchParams.get("combo")) filter.combo = searchParams.get("combo");
if (searchParams.get("search")) filter.search = searchParams.get("search");
+ if (searchParams.get("correlationId")) filter.correlationId = searchParams.get("correlationId");
if (searchParams.get("limit")) filter.limit = parseInt(searchParams.get("limit"));
if (searchParams.get("offset")) filter.offset = parseInt(searchParams.get("offset"));
const [logs, connections] = await Promise.all([getCallLogs(filter), getProviderConnections()]);
- return NextResponse.json(
- buildCallLogListRows({
- logs,
- connections,
- pendingDetails: getPendingById().values(),
- completedDetails: getCompletedDetails().values(),
- })
- );
+ const rows = buildCallLogListRows({
+ logs,
+ connections,
+ pendingDetails: getPendingById().values(),
+ completedDetails: getCompletedDetails().values(),
+ });
+
+ // When correlationId filter is set, also filter in-memory entries
+ // (active + completed) that don't match — getCallLogs already filters
+ // the DB rows but activeEntries/completedEntries bypass it.
+ if (filter.correlationId) {
+ const cid = filter.correlationId;
+ return NextResponse.json(rows.filter((r: any) => r.correlationId === cid));
+ }
+
+ return NextResponse.json(rows);
} catch (error) {
console.error("[API ERROR] /api/usage/call-logs failed:", error);
return NextResponse.json({ error: "Failed to fetch call logs" }, { status: 500 });
diff --git a/src/lib/tokenHealthCheck.ts b/src/lib/tokenHealthCheck.ts
index 5760e41a75..63ff0c5e05 100644
--- a/src/lib/tokenHealthCheck.ts
+++ b/src/lib/tokenHealthCheck.ts
@@ -353,7 +353,8 @@ export async function checkConnection(conn) {
// - transient cooldown state (unavailable) owned by the request path
const refreshCapableNeedsReauth =
supportsTokenRefresh(conn.provider) &&
- (!conn.testStatus || conn.testStatus === "active");
+ (!conn.testStatus || conn.testStatus === "active") &&
+ !(conn.apiKey && conn.apiKey.length > 0); // API-key-only connections don't need refresh tokens
if (refreshCapableNeedsReauth) {
const now = new Date().toISOString();
await updateProviderConnection(conn.id, {
diff --git a/src/shared/components/RequestLoggerDetail.tsx b/src/shared/components/RequestLoggerDetail.tsx
index ecb28345a9..d4d42b0f31 100644
--- a/src/shared/components/RequestLoggerDetail.tsx
+++ b/src/shared/components/RequestLoggerDetail.tsx
@@ -176,6 +176,8 @@ export default function RequestLoggerDetail({
onCopy,
onPrevious,
onNext,
+ relatedLogs = [],
+ onSelectRelated,
}) {
// Close on Escape key
useEffect(() => {
@@ -202,11 +204,12 @@ export default function RequestLoggerDetail({
if (iso == null) return "\u2014";
try {
const d = new Date(iso);
+ if (!Number.isFinite(d.getTime())) return "\u2014";
return (
d.toLocaleDateString("pt-BR") + ", " + d.toLocaleTimeString("en-US", { hour12: false })
);
} catch {
- return iso;
+ return "\u2014";
}
};
@@ -239,32 +242,17 @@ export default function RequestLoggerDetail({
: [];
const requestJson = detail?.requestBody ? toPrettyJson(detail.requestBody) : null;
const responseJson = detail?.responseBody ? toPrettyJson(detail.responseBody) : null;
- const streamChunksText = (() => {
+ const streamChunks = (() => {
if (!debugEnabled || !detail?.pipelinePayloads?.streamChunks) return null;
let chunks: StreamChunks = detail.pipelinePayloads.streamChunks;
-
if (typeof chunks === "string") {
try {
- const parsed = JSON.parse(chunks);
- chunks = parsed;
+ chunks = JSON.parse(chunks);
} catch {
- return chunks;
+ return null;
}
}
-
- if (chunks && typeof chunks === "object") {
- try {
- return Object.entries(chunks)
- .map(([stage, arr]) => {
- const joined = Array.isArray(arr) ? arr.join("") : String(arr);
- return `--- ${stage} ---\n${joined}`;
- })
- .join("\n\n");
- } catch {
- return toPrettyJson(chunks);
- }
- }
-
+ if (chunks && typeof chunks === "object") return chunks;
return null;
})();
const detailIssue =
@@ -346,6 +334,14 @@ export default function RequestLoggerDetail({
{log.id}
)}
+ {log.correlationId && (
+
+ cid: {log.correlationId}
+
+ )}
)}
+ {/* Related Requests (same correlation ID) */}
+ {relatedLogs.length > 1 && (
+
+
+ Related Requests ({relatedLogs.length})
+
+
+ {[...relatedLogs]
+ .sort((a, b) => {
+ const aStart = new Date(a.timestamp).getTime() - (a.duration || 0);
+ const bStart = new Date(b.timestamp).getTime() - (b.duration || 0);
+ return aStart - bStart;
+ })
+ .map((r) => {
+ const rStatusStyle = r.active ? null : getStatusStyle(r.status);
+ const isCurrent = r.id === log.id;
+ const startTime = new Date(new Date(r.timestamp).getTime() - (r.duration || 0));
+ return (
+
+ );
+ })}
+
+
+ )}
+
{detailIssue && (
@@ -612,14 +680,63 @@ export default function RequestLoggerDetail({
) : (
<>
- {streamChunksText && (
+ {streamChunks && streamChunks.provider && (
onCopy(streamChunksText)}
+ title="Provider Event Stream"
+ json={
+ Array.isArray(streamChunks.provider)
+ ? streamChunks.provider.join("")
+ : String(streamChunks.provider)
+ }
+ onCopy={() =>
+ onCopy(
+ Array.isArray(streamChunks.provider)
+ ? streamChunks.provider.join("")
+ : String(streamChunks.provider)
+ )
+ }
/>
)}
+ {streamChunks && streamChunks.client && (
+
+ onCopy(
+ Array.isArray(streamChunks.client)
+ ? streamChunks.client.join("")
+ : String(streamChunks.client)
+ )
+ }
+ />
+ )}
+
+ {streamChunks &&
+ streamChunks.openai &&
+ !streamChunks.provider &&
+ !streamChunks.client && (
+
+ onCopy(
+ Array.isArray(streamChunks.openai)
+ ? streamChunks.openai.join("")
+ : String(streamChunks.openai)
+ )
+ }
+ />
+ )}
+
{payloadSections.length > 0 &&
payloadSections.map((section) => (
{
+ const mapping = columnSortMap[column as keyof typeof columnSortMap];
+ if (!mapping) return;
+ setSortBy((prev) => {
+ if (prev === mapping.desc) return mapping.asc;
+ return mapping.desc;
+ });
+ }, []);
+ const getSortIndicator = useCallback(
+ (column: string) => {
+ const mapping = columnSortMap[column as keyof typeof columnSortMap];
+ if (!mapping) return "";
+ if (sortBy === mapping.desc) return " ↓";
+ if (sortBy === mapping.asc) return " ↑";
+ return "";
+ },
+ [sortBy]
+ );
const [detailData, setDetailData] = useState(null);
const [detailLoggingEnabled, setDetailLoggingEnabled] = useState(false);
const [detailLoggingLoading, setDetailLoggingLoading] = useState(false);
@@ -222,6 +251,7 @@ const RequestLoggerV2 = forwardRef {
+ const cidGroups = new Map();
+ for (const log of sortedLogs) {
+ const cid = log.correlationId;
+ if (cid) {
+ if (!cidGroups.has(cid)) cidGroups.set(cid, []);
+ cidGroups.get(cid).push(log);
+ }
+ }
+ return sortedLogs.map((log) => {
+ const cid = log.correlationId;
+ if (!cid) return { ...log, isRetry: false, groupSize: 1, groupStatus: null };
+ const group = cidGroups.get(cid);
+ if (!group || group.length <= 1)
+ return { ...log, isRetry: false, groupSize: 1, groupStatus: null };
+ const isFirst = group[0].id === log.id;
+ const hasFailure = group.some((g) => g.status >= 400 || g.active);
+ const hasSuccess = group.some((g) => g.status >= 200 && g.status < 300);
+ const groupStatus = hasFailure && hasSuccess ? "healed" : hasFailure ? "failed" : null;
+ return { ...log, isRetry: !isFirst, groupSize: group.length, groupStatus };
+ });
+ }, [sortedLogs]);
+
// Fetch log detail from the persisted call-log endpoint. If a deep-linked
// request is still being finalized, keep the modal open and poll this same
// endpoint until the row appears.
@@ -598,6 +657,50 @@ const RequestLoggerV2 = forwardRef {
+ const cid = selectedLog?.correlationId;
+ if (!selectedLog?.id || !cid) return;
+ let cancelled = false;
+ const interval = setInterval(async () => {
+ if (document.visibilityState !== "visible") return;
+ try {
+ const res = await fetch(`/api/usage/call-logs?correlationId=${encodeURIComponent(cid)}`, {
+ cache: "no-store",
+ });
+ if (cancelled || !res.ok) return;
+ const cidLogs = await res.json();
+ if (!Array.isArray(cidLogs) || cidLogs.length === 0) return;
+ setLogs((prev) => {
+ const ids = new Set(cidLogs.map((l: any) => l.id));
+ let changed = false;
+ const merged = prev.map((l: any) => {
+ const updated = cidLogs.find((c: any) => c.id === l.id);
+ if (updated) {
+ changed = true;
+ return { ...l, ...updated };
+ }
+ return l;
+ });
+ for (const cl of cidLogs) {
+ if (!merged.some((m: any) => m.id === cl.id)) {
+ merged.push(cl);
+ changed = true;
+ }
+ }
+ return changed ? merged : prev;
+ });
+ } catch {
+ /* poll failed — non-critical */
+ }
+ }, 3000);
+ return () => {
+ cancelled = true;
+ clearInterval(interval);
+ };
+ }, [selectedLog?.id, selectedLog?.correlationId]);
+
const currentLogIndex = useMemo(() => {
if (!selectedLog) return -1;
return sortedLogsForNav.findIndex((l) => l.id === selectedLog.id);
@@ -685,13 +788,14 @@ const RequestLoggerV2 = forwardRef ({
totalCount: filteredLogs.length,
okCount: filteredLogs.filter((l) => l.status >= 200 && l.status < 300).length,
errorCount: filteredLogs.filter((l) => l.status >= 400).length,
comboCount: logs.filter((l) => l.comboName).length,
apiKeyCount: uniqueApiKeys.length,
+ runningCount: filteredLogs.filter((l) => l.active === true).length,
}),
[filteredLogs, logs, uniqueApiKeys]
);
@@ -749,6 +853,20 @@ const RequestLoggerV2 = forwardRef
+ {/* Correlation ID Filter */}
+
+
+ tag
+
+ setCorrelationIdFilter(e.target.value)}
+ className="w-full pl-9 pr-3 py-2 rounded-lg bg-bg-subtle border border-border text-sm text-text-primary font-mono placeholder:text-text-muted focus:outline-none focus:border-primary"
+ />
+
+
{/* Provider Dropdown */}