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
2 changes: 2 additions & 0 deletions messages/en/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"noProviderFound": "No matching providers found",
"noSessionFound": "No matching session IDs found",
"model": "Model",
"actualResponseModelMismatch": "Billing/actual model mismatch",
"endpoint": "Endpoint",
"status": "Status",
"timeRange": "Time Range",
Expand All @@ -86,6 +87,7 @@
"statusCode": "Status Code",
"minRetryCount": "Retry Count ≥",
"minRetryCountPlaceholder": "Enter minimum retries",
"enabled": "On",
"apply": "Apply Filter",
"reset": "Reset",
"last7days": "7d",
Expand Down
2 changes: 2 additions & 0 deletions messages/ja/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"noProviderFound": "一致するプロバイダーが見つかりません",
"noSessionFound": "一致するセッションIDが見つかりません",
"model": "モデル",
"actualResponseModelMismatch": "課金/実応答モデル不一致",
"endpoint": "エンドポイント",
"status": "ステータス",
"timeRange": "時間範囲",
Expand All @@ -86,6 +87,7 @@
"statusCode": "ステータスコード",
"minRetryCount": "リトライ回数≥",
"minRetryCountPlaceholder": "回数を入力(0 で制限なし)",
"enabled": "オン",
"apply": "フィルターを適用",
"reset": "リセット",
"last7days": "7日",
Expand Down
2 changes: 2 additions & 0 deletions messages/ru/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"noProviderFound": "Провайдеры не найдены",
"noSessionFound": "ID сессии не найдены",
"model": "Модель",
"actualResponseModelMismatch": "Модель тарификации отличается от фактической",
"endpoint": "Эндпоинт",
"status": "Статус",
"timeRange": "Диапазон времени",
Expand All @@ -86,6 +87,7 @@
"statusCode": "Код состояния",
"minRetryCount": "Количество ретраев ≥",
"minRetryCountPlaceholder": "Введите минимум (0 — без ограничения)",
"enabled": "Вкл.",
"apply": "Применить фильтр",
"reset": "Сброс",
"last7days": "7д",
Expand Down
2 changes: 2 additions & 0 deletions messages/zh-CN/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"noProviderFound": "未找到匹配的供应商",
"noSessionFound": "未找到匹配的 Session ID",
"model": "模型",
"actualResponseModelMismatch": "计费/实际模型不一致",
"endpoint": "端点",
"status": "状态",
"timeRange": "时间范围",
Expand All @@ -86,6 +87,7 @@
"statusCode": "状态码",
"minRetryCount": "重试次数≥",
"minRetryCountPlaceholder": "输入次数(0 表示不限)",
"enabled": "已开启",
"apply": "应用筛选",
"reset": "重置",
"last7days": "近7天",
Expand Down
2 changes: 2 additions & 0 deletions messages/zh-TW/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"noProviderFound": "未找到匹配的供應商",
"noSessionFound": "未找到符合的 Session ID",
"model": "Model",
"actualResponseModelMismatch": "計費/實際模型不一致",
"endpoint": "端點",
"status": "狀態",
"timeRange": "時間範圍",
Expand All @@ -86,6 +87,7 @@
"statusCode": "狀態碼",
"minRetryCount": "重試次數≥",
"minRetryCountPlaceholder": "輸入次數(0 表示不限)",
"enabled": "已開啟",
"apply": "套用篩選",
"reset": "重設",
"last7days": "近 7 天",
Expand Down
5 changes: 5 additions & 0 deletions src/actions/my-usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ export interface MyUsageLogsFilters {
endTime?: number;
sessionId?: string;
model?: string;
actualResponseModelMismatch?: boolean;
statusCode?: number;
excludeStatusCode200?: boolean;
endpoint?: string;
Expand Down Expand Up @@ -615,6 +616,7 @@ export interface MyUsageLogsBatchFilters {
/** Session ID(精确匹配;空字符串/空白视为不筛选) */
sessionId?: string;
model?: string;
actualResponseModelMismatch?: boolean;
statusCode?: number;
excludeStatusCode200?: boolean;
endpoint?: string;
Expand Down Expand Up @@ -684,6 +686,7 @@ export async function getMyUsageLogs(
startTime: dateRange.startTime,
endTime: dateRange.endTime,
model: filters.model,
actualResponseModelMismatch: filters.actualResponseModelMismatch,
statusCode: filters.statusCode,
excludeStatusCode200: filters.excludeStatusCode200,
endpoint: filters.endpoint,
Expand Down Expand Up @@ -729,6 +732,7 @@ export async function getMyUsageLogsBatch(
startTime: dateRange.startTime,
endTime: dateRange.endTime,
model: filters.model,
actualResponseModelMismatch: filters.actualResponseModelMismatch,
statusCode: filters.statusCode,
excludeStatusCode200: filters.excludeStatusCode200,
endpoint: filters.endpoint,
Expand Down Expand Up @@ -777,6 +781,7 @@ export async function getMyUsageLogsBatchFull(
const result = await findReadonlyUsageLogsBatchForKey({
sessionId: params.sessionId,
model: params.model,
actualResponseModelMismatch: params.actualResponseModelMismatch,
statusCode: params.statusCode,
excludeStatusCode200: params.excludeStatusCode200,
endpoint: params.endpoint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ export function ActiveFiltersDisplay({
});
}

if (filters.actualResponseModelMismatch) {
result.push({
key: "actualResponseModelMismatch",
label: t("actualResponseModelMismatch"),
value: t("enabled"),
});
}

// Endpoint filter
if (filters.endpoint) {
result.push({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { getUsageLogSessionIdSuggestions } from "@/lib/api-client/v1/actions/usage-logs";
import { SESSION_ID_SUGGESTION_MIN_LEN } from "@/lib/constants/usage-logs.constants";
import { useDebounce } from "@/lib/hooks/use-debounce";
Expand Down Expand Up @@ -303,6 +304,23 @@ export function RequestFilters({
</Select>
</div>

<div className="flex items-center justify-between gap-3 rounded-md border border-input bg-background px-3 py-2">
<Label htmlFor="actual-response-model-mismatch-filter" className="text-sm font-normal">
{t("logs.filters.actualResponseModelMismatch")}
</Label>
<Switch
id="actual-response-model-mismatch-filter"
checked={Boolean(filters.actualResponseModelMismatch)}
onCheckedChange={(checked) =>
onFiltersChange({
...filters,
actualResponseModelMismatch: checked ? true : undefined,
})
}
aria-label={t("logs.filters.actualResponseModelMismatch")}
/>
</div>

{/* Session ID with suggestions */}
<div className="space-y-2">
<Label>{t("logs.filters.sessionId")}</Label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface UsageLogFilters {
statusCode?: number;
excludeStatusCode200?: boolean;
model?: string;
actualResponseModelMismatch?: boolean;
endpoint?: string;
minRetryCount?: number;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const VALID_FILTER_KEYS: (keyof UsageLogFilters)[] = [
"statusCode",
"excludeStatusCode200",
"model",
"actualResponseModelMismatch",
"endpoint",
"minRetryCount",
];
Expand Down Expand Up @@ -131,13 +132,15 @@ export function UsageLogsFilters({
let count = 0;
if (isAdmin && localFilters.providerId !== undefined) count++;
if (localFilters.model) count++;
if (localFilters.actualResponseModelMismatch) count++;
if (localFilters.endpoint) count++;
if (localFilters.sessionId) count++;
return count;
}, [
isAdmin,
localFilters.providerId,
localFilters.model,
localFilters.actualResponseModelMismatch,
localFilters.endpoint,
localFilters.sessionId,
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ vi.mock("./usage-logs-filters", () => ({
endTime: 2000,
statusCode: 500,
model: "claude-sonnet",
actualResponseModelMismatch: true,
endpoint: "/v1/messages",
minRetryCount: 1,
})
Expand Down Expand Up @@ -185,7 +186,7 @@ describe("UsageLogsViewVirtualized filter navigation", () => {
clickButton(container, "apply all filters");

expect(routerMocks.pushedHref).toBe(
"/zh-CN/dashboard/logs?userId=2&keyId=3&providerId=4&sessionId=session-abc&startTime=1000&endTime=2000&statusCode=500&model=claude-sonnet&endpoint=%2Fv1%2Fmessages&minRetry=1"
"/zh-CN/dashboard/logs?userId=2&keyId=3&providerId=4&sessionId=session-abc&startTime=1000&endTime=2000&statusCode=500&model=claude-sonnet&actualResponseModelMismatch=true&endpoint=%2Fv1%2Fmessages&minRetry=1"
);

unmount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ function UsageLogsViewContent({
endTime: _params.get("endTime") ?? undefined,
statusCode: _params.get("statusCode") ?? undefined,
model: _params.get("model") ?? undefined,
actualResponseModelMismatch: _params.get("actualResponseModelMismatch") ?? undefined,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The parameter actualResponseModelMismatch is retrieved directly from URLSearchParams using _params.get("actualResponseModelMismatch") ?? undefined. This returns a string (e.g., "true" or "false") or undefined, rather than a boolean.

This causes a TypeScript type mismatch if the filter is typed as boolean | undefined. Furthermore, it can lead to subtle runtime bugs because the string "false" is truthy in JavaScript, which would cause the filter to be treated as active even when set to false.

We should explicitly parse the string value to a boolean.

Suggested change
actualResponseModelMismatch: _params.get("actualResponseModelMismatch") ?? undefined,
actualResponseModelMismatch: _params.get("actualResponseModelMismatch") === "true" ? true : undefined,

endpoint: _params.get("endpoint") ?? undefined,
minRetry: _params.get("minRetry") ?? undefined,
page: _params.get("page") ?? undefined,
Expand Down Expand Up @@ -265,6 +266,7 @@ function UsageLogsViewContent({
if (statsFilters.sessionId) count++;
if (statsFilters.statusCode !== undefined || statsFilters.excludeStatusCode200) count++;
if (statsFilters.model) count++;
if (statsFilters.actualResponseModelMismatch) count++;
if (statsFilters.endpoint) count++;
if (statsFilters.minRetryCount !== undefined && statsFilters.minRetryCount > 0) count++;
return count;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export interface VirtualizedLogsTableFilters {
statusCode?: number;
excludeStatusCode200?: boolean;
model?: string;
actualResponseModelMismatch?: boolean;
endpoint?: string;
minRetryCount?: number;
}
Expand Down
6 changes: 6 additions & 0 deletions src/app/[locale]/dashboard/logs/_utils/logs-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface LogsUrlFilters {
statusCode?: number;
excludeStatusCode200?: boolean;
model?: string;
actualResponseModelMismatch?: boolean;
endpoint?: string;
minRetryCount?: number;
page?: number;
Expand Down Expand Up @@ -43,6 +44,9 @@ export function parseLogsUrlFilters(searchParams: {
? Number.parseInt(statusCodeParam, 10)
: undefined;

const actualResponseModelMismatch =
parseStringParam(searchParams.actualResponseModelMismatch) === "true" ? true : undefined;

return {
userId: parseIntParam(searchParams.userId),
keyId: parseIntParam(searchParams.keyId),
Expand All @@ -53,6 +57,7 @@ export function parseLogsUrlFilters(searchParams: {
statusCode: Number.isFinite(statusCode) ? statusCode : undefined,
excludeStatusCode200: statusCodeParam === "!200",
model: parseStringParam(searchParams.model),
actualResponseModelMismatch,
endpoint: parseStringParam(searchParams.endpoint),
minRetryCount: parseIntParam(searchParams.minRetry),
page,
Expand All @@ -79,6 +84,7 @@ export function buildLogsUrlQuery(filters: LogsUrlFilters): URLSearchParams {
}

if (filters.model) query.set("model", filters.model);
if (filters.actualResponseModelMismatch) query.set("actualResponseModelMismatch", "true");
if (filters.endpoint) query.set("endpoint", filters.endpoint);

if (filters.minRetryCount !== undefined) {
Expand Down
1 change: 1 addition & 0 deletions src/app/api/v1/resources/me/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ function parseMeUsageLogsQuery(c: Context): MeUsageLogsActionQueryInput | Respon
endTime: c.req.query("endTime"),
sessionId: c.req.query("sessionId"),
model: c.req.query("model"),
actualResponseModelMismatch: c.req.query("actualResponseModelMismatch"),
statusCode: c.req.query("statusCode"),
excludeStatusCode200: c.req.query("excludeStatusCode200"),
endpoint: c.req.query("endpoint"),
Expand Down
1 change: 1 addition & 0 deletions src/app/api/v1/resources/usage-logs/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ function parseUsageLogsQuery(c: Context): UsageLogsActionQueryInput | Response {
keyId: c.req.query("keyId"),
providerId: c.req.query("providerId"),
model: c.req.query("model"),
actualResponseModelMismatch: c.req.query("actualResponseModelMismatch"),
statusCode: c.req.query("statusCode"),
excludeStatusCode200: c.req.query("excludeStatusCode200"),
endpoint: c.req.query("endpoint"),
Expand Down
10 changes: 10 additions & 0 deletions src/lib/api-client/v1/openapi-types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35295,6 +35295,8 @@ export interface operations {
providerId?: number | null;
/** @description Model filter. */
model?: string;
/** @description Only include records whose requested model differs from the actual response model. */
actualResponseModelMismatch?: "true" | "false" | boolean;
/** @description HTTP status code filter. */
statusCode?: number | null;
/** @description Exclude successful responses. */
Expand Down Expand Up @@ -35498,6 +35500,8 @@ export interface operations {
providerId?: number | null;
/** @description Model filter. */
model?: string;
/** @description Only include records whose requested model differs from the actual response model. */
actualResponseModelMismatch?: "true" | "false" | boolean;
/** @description HTTP status code filter. */
statusCode?: number | null;
/** @description Exclude successful responses. */
Expand Down Expand Up @@ -36566,6 +36570,8 @@ export interface operations {
providerId?: number | null;
/** @description Model filter. */
model?: string;
/** @description Only include records whose requested model differs from the actual response model. */
actualResponseModelMismatch?: "true" | "false" | boolean;
/** @description HTTP status code filter. */
statusCode?: number | null;
/** @description Exclude successful responses. */
Expand Down Expand Up @@ -37642,6 +37648,8 @@ export interface operations {
sessionId?: string;
/** @description Model filter. */
model?: string;
/** @description Only include records whose requested model differs from the actual response model. */
actualResponseModelMismatch?: "true" | "false" | boolean;
/** @description HTTP status code filter. */
statusCode?: number | null;
/** @description Exclude successful responses. */
Expand Down Expand Up @@ -37843,6 +37851,8 @@ export interface operations {
sessionId?: string;
/** @description Model filter. */
model?: string;
/** @description Only include records whose requested model differs from the actual response model. */
actualResponseModelMismatch?: "true" | "false" | boolean;
/** @description HTTP status code filter. */
statusCode?: number | null;
/** @description Exclude successful responses. */
Expand Down
3 changes: 3 additions & 0 deletions src/lib/api/v1/schemas/me.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export const MeUsageLogsQuerySchema = z.object({
endTime: NumberQuerySchema.describe("End timestamp in milliseconds."),
sessionId: z.string().optional().describe("Session id filter."),
model: z.string().optional().describe("Model filter."),
actualResponseModelMismatch: BooleanQuerySchema.describe(
"Only include records whose requested model differs from the actual response model."
),
statusCode: z.coerce.number().int().optional().describe("HTTP status code filter."),
excludeStatusCode200: BooleanQuerySchema.describe("Exclude successful responses."),
endpoint: z.string().optional().describe("Endpoint filter."),
Expand Down
3 changes: 3 additions & 0 deletions src/lib/api/v1/schemas/usage-logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export const UsageLogsQuerySchema = z.object({
keyId: NumberQuerySchema.describe("Key id filter."),
providerId: NumberQuerySchema.describe("Provider id filter."),
model: z.string().optional().describe("Model filter."),
actualResponseModelMismatch: BooleanQuerySchema.describe(
"Only include records whose requested model differs from the actual response model."
),
statusCode: z.coerce.number().int().optional().describe("HTTP status code filter."),
excludeStatusCode200: BooleanQuerySchema.describe("Exclude successful responses."),
endpoint: z.string().optional().describe("Endpoint filter."),
Expand Down
Loading
Loading