File size: 3,775 Bytes
35743bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import {
  PUBLIC_API_ROUTE_PREFIXES,
  PUBLIC_READONLY_API_ROUTE_PREFIXES,
  PUBLIC_READONLY_METHODS,
} from "../../shared/constants/publicApiRoutes";
import type { ClassificationReason, RouteClass, RouteClassification } from "./types";

const CLIENT_API_ALIAS_PREFIXES: ReadonlyArray<{ alias: string; canonical: string }> = [
  { alias: "/chat/completions", canonical: "/api/v1/chat/completions" },
  { alias: "/responses", canonical: "/api/v1/responses" },
  { alias: "/models", canonical: "/api/v1/models" },
];

function normalizePathname(rawPath: string): { path: string; reason?: ClassificationReason } {
  let path = rawPath || "/";
  if (!path.startsWith("/")) path = "/" + path;
  if (path.length > 1 && path.endsWith("/")) path = path.slice(0, -1);

  if (path === "/codex" || path.startsWith("/codex/")) {
    return { path: "/api/v1/responses", reason: "client_api_codex_alias" };
  }

  if (path === "/v1/v1" || path.startsWith("/v1/v1/")) {
    const tail = path.slice("/v1/v1".length) || "";
    return { path: "/api/v1" + tail, reason: "client_api_double_prefix" };
  }

  if (path === "/v1" || path.startsWith("/v1/")) {
    const tail = path.slice("/v1".length) || "";
    return { path: "/api/v1" + tail, reason: "client_api_alias" };
  }

  for (const { alias, canonical } of CLIENT_API_ALIAS_PREFIXES) {
    if (path === alias) {
      return { path: canonical, reason: "client_api_alias" };
    }
    if (path.startsWith(alias + "/")) {
      return { path: canonical + path.slice(alias.length), reason: "client_api_alias" };
    }
  }

  return { path };
}

export function classifyRoute(rawPath: string, method: string = "GET"): RouteClassification {
  const { path: normalizedPath, reason: aliasReason } = normalizePathname(rawPath);

  if (normalizedPath === "/" || normalizedPath === "") {
    return {
      routeClass: "MANAGEMENT",
      reason: "root_redirect",
      normalizedPath: "/",
    };
  }

  if (normalizedPath.startsWith("/dashboard")) {
    return {
      routeClass: "MANAGEMENT",
      reason: "dashboard_prefix",
      normalizedPath,
    };
  }

  if (normalizedPath === "/api/v1" || normalizedPath.startsWith("/api/v1/")) {
    return {
      routeClass: "CLIENT_API",
      reason: aliasReason ?? "client_api_v1",
      normalizedPath,
    };
  }

  if (normalizedPath.startsWith("/api/")) {
    if (isClassifiedAsPublic(normalizedPath, method)) {
      return {
        routeClass: "PUBLIC",
        reason: matchesReadonlyPublic(normalizedPath, method)
          ? "public_readonly_prefix"
          : "public_prefix",
        normalizedPath,
      };
    }

    return {
      routeClass: "MANAGEMENT",
      reason: "management_api",
      normalizedPath,
    };
  }

  return {
    routeClass: "MANAGEMENT",
    reason: "fallback_management",
    normalizedPath,
  };
}

function matchesReadonlyPublic(path: string, method: string): boolean {
  if (!PUBLIC_READONLY_METHODS.has(String(method).toUpperCase())) return false;
  return PUBLIC_READONLY_API_ROUTE_PREFIXES.some((p) => path.startsWith(p));
}

function isClassifiedAsPublic(path: string, method: string): boolean {
  const isV1ApiPrefix = (p: string) =>
    p === "/api/v1" || p === "/api/v1/" || p.startsWith("/api/v1/");
  const filtered = PUBLIC_API_ROUTE_PREFIXES.filter((p) => p !== "/api/v1/");
  if (filtered.some((prefix) => path.startsWith(prefix)) && !isV1ApiPrefix(path)) {
    return true;
  }
  return matchesReadonlyPublic(path, method);
}

export function isClientApi(routeClass: RouteClass): boolean {
  return routeClass === "CLIENT_API";
}

export function isManagement(routeClass: RouteClass): boolean {
  return routeClass === "MANAGEMENT";
}

export function isPublic(routeClass: RouteClass): boolean {
  return routeClass === "PUBLIC";
}