Skip to content

Commit 889e2ba

Browse files
committed
feat: add error pages, harden DB layer and compliance module
- Add HTTP error pages (400, 401, 403, 408, 429, 500, 502, 503) - Add maintenance, offline, and system status pages - Harden db/core.ts, db/apiKeys.ts, db/cliToolState.ts, db/backup.ts - Strengthen compliance/index.ts audit logging - Improve container.ts DI registrations - Fix dataPaths.ts and tokenHealthCheck.ts
1 parent 500cae3 commit 889e2ba

File tree

20 files changed

+1019
-175
lines changed

20 files changed

+1019
-175
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* Agent Card Endpoint — /.well-known/agent.json
3+
*
4+
* Serves the OmniRoute A2A Agent Card for discovery by other agents.
5+
* Conforms to A2A Protocol v0.3.
6+
*
7+
* The Agent Card is dynamically generated to include the current version
8+
* from package.json and skills based on available combos.
9+
*/
10+
11+
import { NextResponse } from "next/server";
12+
13+
const PACKAGE_VERSION = process.env.npm_package_version || "1.8.1";
14+
const BASE_URL = process.env.OMNIROUTE_BASE_URL || "http://localhost:20128";
15+
16+
/**
17+
* GET /.well-known/agent.json
18+
*
19+
* Returns the OmniRoute Agent Card that describes this gateway's
20+
* capabilities as an A2A agent.
21+
*/
22+
export async function GET() {
23+
const agentCard = {
24+
name: "OmniRoute AI Gateway",
25+
description:
26+
"Intelligent AI routing gateway with 36+ providers, smart fallback, " +
27+
"quota tracking, format translation, and auto-managed combos. " +
28+
"Routes AI requests to the optimal provider based on cost, latency, " +
29+
"quota availability, and task requirements.",
30+
url: `${BASE_URL}/a2a`,
31+
version: PACKAGE_VERSION,
32+
capabilities: {
33+
streaming: true,
34+
pushNotifications: false,
35+
},
36+
skills: [
37+
{
38+
id: "smart-routing",
39+
name: "Smart Request Routing",
40+
description:
41+
"Routes AI requests to the optimal provider based on quota, cost, " +
42+
"latency, and reliability. Supports combo-based routing with " +
43+
"multiple strategies: priority, weighted, round-robin, cost-optimized.",
44+
tags: ["routing", "llm", "optimization", "fallback"],
45+
examples: [
46+
"Route this coding task to the fastest available model",
47+
"Send this review to an analytical model under $0.50 budget",
48+
"Find the cheapest provider with available quota",
49+
],
50+
},
51+
{
52+
id: "quota-management",
53+
name: "Quota & Cost Management",
54+
description:
55+
"Tracks and manages API quotas across 36+ providers with " +
56+
"auto-fallback when quotas are exhausted. Provides real-time " +
57+
"cost tracking and budget enforcement.",
58+
tags: ["quota", "cost", "monitoring", "budget"],
59+
examples: [
60+
"Check remaining quota for all providers",
61+
"Which provider has the most available quota?",
62+
"Generate a cost report for today",
63+
],
64+
},
65+
{
66+
id: "auto-combo",
67+
name: "Auto-Managed Model Combos",
68+
description:
69+
"Self-healing model chains that dynamically adapt to provider " +
70+
"health and quota availability. Uses a scoring function based on " +
71+
"quota, health, cost, latency, task fitness, and stability.",
72+
tags: ["combo", "auto", "self-healing", "adaptive"],
73+
examples: [
74+
"Create an auto-managed combo for coding tasks",
75+
"Switch to cost-saver mode",
76+
"Show the Auto-Combo scoring breakdown",
77+
],
78+
},
79+
{
80+
id: "format-translation",
81+
name: "Format Translation",
82+
description:
83+
"Transparently translates between OpenAI, Claude (Anthropic), " +
84+
"Gemini (Google), and Responses API formats. Supports streaming " +
85+
"translation for all format pairs.",
86+
tags: ["translation", "openai", "claude", "gemini", "responses"],
87+
examples: [
88+
"Send an OpenAI-format request to Claude",
89+
"Translate this Gemini response to OpenAI format",
90+
],
91+
},
92+
],
93+
authentication: {
94+
schemes: ["api-key"],
95+
apiKeyHeader: "Authorization",
96+
},
97+
};
98+
99+
return NextResponse.json(agentCard, {
100+
headers: {
101+
"Cache-Control": "public, max-age=3600",
102+
"Content-Type": "application/json",
103+
},
104+
});
105+
}

src/app/400/page.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import ErrorPageScaffold from "@/shared/components/ErrorPageScaffold";
2+
3+
export default function BadRequestPage() {
4+
return (
5+
<ErrorPageScaffold
6+
code="400"
7+
icon="rule"
8+
title="Bad Request"
9+
description="The request payload is invalid or incomplete."
10+
suggestions={[
11+
"Review required fields and payload format before retrying.",
12+
"If you are using the API, validate the JSON schema locally.",
13+
"If this keeps happening, open the request in Translator Playground to inspect the payload.",
14+
]}
15+
primaryAction={{ href: "/docs", label: "Open Documentation" }}
16+
secondaryAction={{ href: "/dashboard/translator", label: "Open Translator" }}
17+
/>
18+
);
19+
}

src/app/401/page.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import ErrorPageScaffold from "@/shared/components/ErrorPageScaffold";
2+
3+
export default function UnauthorizedPage() {
4+
return (
5+
<ErrorPageScaffold
6+
code="401"
7+
icon="lock"
8+
title="Unauthorized"
9+
description="Authentication is required to access this resource."
10+
suggestions={[
11+
"Sign in again and retry the operation.",
12+
"For API calls, confirm the Bearer token is present and valid.",
13+
"If the token was recently rotated, update your client credentials.",
14+
]}
15+
primaryAction={{ href: "/login", label: "Go to Login" }}
16+
secondaryAction={{ href: "/dashboard/api-manager", label: "Manage API Keys" }}
17+
/>
18+
);
19+
}

src/app/403/page.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import ErrorPageScaffold from "@/shared/components/ErrorPageScaffold";
2+
3+
export default function ForbiddenStatusPage() {
4+
return (
5+
<ErrorPageScaffold
6+
code="403"
7+
icon="gpp_bad"
8+
title="Forbidden"
9+
description="Your request was understood, but access is denied by policy."
10+
suggestions={[
11+
"Check IP allowlist/blocklist rules in settings.",
12+
"Verify model and budget policies assigned to your API key.",
13+
"Ask an administrator to grant the required permission scope.",
14+
]}
15+
primaryAction={{ href: "/forbidden", label: "Open Access Help" }}
16+
secondaryAction={{
17+
href: "/dashboard/settings?tab=security",
18+
label: "Open Security Settings",
19+
}}
20+
/>
21+
);
22+
}

src/app/408/page.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import ErrorPageScaffold from "@/shared/components/ErrorPageScaffold";
2+
3+
export default function RequestTimeoutPage() {
4+
return (
5+
<ErrorPageScaffold
6+
code="408"
7+
icon="timer_off"
8+
title="Request Timeout"
9+
description="The server did not receive a complete request in time."
10+
suggestions={[
11+
"Retry the request with a smaller payload.",
12+
"Check your network stability and VPN/proxy latency.",
13+
"For long operations, enable streaming or split the request.",
14+
]}
15+
primaryAction={{ href: "/dashboard/endpoint", label: "Open Endpoint Guide" }}
16+
secondaryAction={{ href: "/status", label: "Check Network Status" }}
17+
/>
18+
);
19+
}

src/app/429/page.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import ErrorPageScaffold from "@/shared/components/ErrorPageScaffold";
2+
3+
export default function TooManyRequestsPage() {
4+
return (
5+
<ErrorPageScaffold
6+
code="429"
7+
icon="hourglass_top"
8+
title="Too Many Requests"
9+
description="Rate limits were exceeded for this client, key, or provider."
10+
suggestions={[
11+
"Wait for cooldown and retry after the suggested interval.",
12+
"Switch to a combo with fallback providers.",
13+
"Tune provider resilience/rate-limit profiles in settings.",
14+
]}
15+
primaryAction={{
16+
href: "/dashboard/settings?tab=resilience",
17+
label: "Open Resilience Settings",
18+
}}
19+
secondaryAction={{ href: "/dashboard/combos", label: "Open Combos" }}
20+
/>
21+
);
22+
}

src/app/500/page.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import ErrorPageScaffold from "@/shared/components/ErrorPageScaffold";
2+
3+
export default function InternalServerErrorPage() {
4+
return (
5+
<ErrorPageScaffold
6+
code="500"
7+
icon="warning"
8+
title="Internal Server Error"
9+
description="An unexpected server-side error occurred while processing your request."
10+
suggestions={[
11+
"Retry once in a few seconds.",
12+
"Check health telemetry and server logs for correlated request IDs.",
13+
"If persistent, report the issue with timestamp and request context.",
14+
]}
15+
primaryAction={{ href: "/dashboard/health", label: "Open Health Dashboard" }}
16+
secondaryAction={{ href: "/dashboard/logs", label: "Open Logs" }}
17+
/>
18+
);
19+
}

src/app/502/page.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import ErrorPageScaffold from "@/shared/components/ErrorPageScaffold";
2+
3+
export default function BadGatewayPage() {
4+
return (
5+
<ErrorPageScaffold
6+
code="502"
7+
icon="hub"
8+
title="Bad Gateway"
9+
description="Upstream provider or gateway integration returned an invalid response."
10+
suggestions={[
11+
"Retry with another provider or active combo route.",
12+
"Check provider credentials and model availability.",
13+
"Inspect translator output if format conversion is involved.",
14+
]}
15+
primaryAction={{ href: "/dashboard/providers", label: "Open Providers" }}
16+
secondaryAction={{ href: "/dashboard/translator", label: "Open Translator" }}
17+
/>
18+
);
19+
}

src/app/503/page.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import ErrorPageScaffold from "@/shared/components/ErrorPageScaffold";
2+
3+
export default function ServiceUnavailablePage() {
4+
return (
5+
<ErrorPageScaffold
6+
code="503"
7+
icon="build_circle"
8+
title="Service Unavailable"
9+
description="The service is temporarily unavailable due to maintenance or degraded dependencies."
10+
suggestions={[
11+
"Wait a moment and retry.",
12+
"Check maintenance notices and system status.",
13+
"Use fallback providers if your workflow is latency-sensitive.",
14+
]}
15+
primaryAction={{ href: "/maintenance", label: "Maintenance Details" }}
16+
secondaryAction={{ href: "/status", label: "System Status" }}
17+
/>
18+
);
19+
}

src/app/maintenance/page.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import Link from "next/link";
2+
3+
export default function MaintenancePage() {
4+
return (
5+
<main className="min-h-screen bg-bg text-text-main flex items-center justify-center p-6">
6+
<section className="w-full max-w-xl rounded-2xl border border-border bg-surface p-8 shadow-soft text-center">
7+
<span className="material-symbols-outlined text-5xl text-primary mb-3" aria-hidden="true">
8+
construction
9+
</span>
10+
<h1 className="text-2xl font-semibold">Scheduled Maintenance</h1>
11+
<p className="mt-3 text-text-muted leading-relaxed">
12+
Some services are temporarily unavailable while maintenance is in progress. Core routing
13+
usually remains online, but management features may be degraded.
14+
</p>
15+
16+
<ul className="mt-6 text-sm text-text-muted text-left rounded-xl border border-border bg-bg-alt p-4 space-y-2">
17+
<li className="flex items-start gap-2">
18+
<span
19+
className="material-symbols-outlined text-base text-primary mt-0.5"
20+
aria-hidden="true"
21+
>
22+
info
23+
</span>
24+
Retry after a few minutes.
25+
</li>
26+
<li className="flex items-start gap-2">
27+
<span
28+
className="material-symbols-outlined text-base text-primary mt-0.5"
29+
aria-hidden="true"
30+
>
31+
info
32+
</span>
33+
Check current health indicators and provider status before retrying.
34+
</li>
35+
</ul>
36+
37+
<div className="mt-8 flex flex-col sm:flex-row gap-3">
38+
<Link
39+
href="/status"
40+
className="inline-flex items-center justify-center px-6 py-3 rounded-lg text-white text-sm font-semibold bg-gradient-to-br from-primary to-primary-hover hover:shadow-elevated transition-all duration-200 motion-reduce:transition-none"
41+
>
42+
View System Status
43+
</Link>
44+
<Link
45+
href="/dashboard/health"
46+
className="inline-flex items-center justify-center px-6 py-3 rounded-lg text-sm font-semibold border border-border hover:bg-bg-alt transition-colors duration-200 motion-reduce:transition-none"
47+
>
48+
Open Health Dashboard
49+
</Link>
50+
</div>
51+
</section>
52+
</main>
53+
);
54+
}

0 commit comments

Comments
 (0)