Skip to content

g1t/apps/web/app/lib/gateway.ts

98 lines4,397 bytesCodeBlame
1/**
2 * How the AI Gateway page speaks of its requests. Pure, so it can be tested.
3 */
4
5import type { GatewayRequest } from "@g1t/contracts";
6
7/** Where the AI Gateway is documented. */
8export const GATEWAY_DOCS = "https://docs.g1t.sh/guides/ai-gateway/";
9
10/** The base URL an Anthropic SDK or Claude Code is pointed at. */
11export const GATEWAY_BASE_URL = "https://models.g1t.sh/anthropic";
12
13/** The base URL an OpenAI SDK, or any tool that speaks OpenAI's API, is pointed at. */
14export const GATEWAY_OPENAI_BASE_URL = "https://models.g1t.sh/openai/v1";
15
16/** How a request's format reads. */
17export function formatLabel(format: GatewayRequest["format"] | undefined): string {
18 return format === "openai" ? "OpenAI" : "Anthropic";
19}
20
21/** Who served a request, as people read it, and a longer line for its hint. */
22export function servedBy(request: Pick<GatewayRequest, "ownKey" | "provider" | "connection">): { label: string; hint: string } {
23 if (request.ownKey) {
24 const name = request.connection || "Your provider";
25 return { label: name, hint: `${name}, the workspace's own ${providerName(request.provider)}: counted, not charged.` };
26 }
27 if (!request.provider) return { label: "None", hint: "Refused before it reached a model." };
28 return { label: `g1t · ${providerName(request.provider)}`, hint: `g1t's account at ${providerName(request.provider)}, charged at the model's price.` };
29}
30
31const PROVIDER_NAMES: Record<string, string> = {
32 anthropic: "Anthropic",
33 "workers-ai": "Workers AI",
34 openai: "OpenAI",
35 openai_endpoint: "OpenAI-compatible endpoint",
36 anthropic_endpoint: "Anthropic-compatible endpoint",
37 azure_openai: "Azure OpenAI",
38 gemini: "Google Gemini",
39 openrouter: "OpenRouter",
40};
41
42/** A provider's name, as people know it. */
43export function providerName(provider: string): string {
44 return PROVIDER_NAMES[provider] ?? (provider || "provider");
45}
46
47/** A connection's AI Gateway models, as a field shows them, from what was typed. */
48export function parseGatewayModels(text: string): string[] {
49 return text
50 .split(/[\s,]+/)
51 .map((model) => model.trim())
52 .filter(Boolean);
53}
54
55/** A count of tokens, short: `812`, `12.4K`, `3.1M`. */
56export function shortCount(n: number): string {
57 if (n < 1_000) return String(n);
58 if (n < 1_000_000) return `${trim(n / 1_000)}K`;
59 return `${trim(n / 1_000_000)}M`;
60}
61
62function trim(value: number): string {
63 return value >= 100 ? String(Math.round(value)) : value.toFixed(1).replace(/\.0$/, "");
64}
65
66/** Every token a request used. */
67export function totalTokens(request: Pick<GatewayRequest, "input" | "output" | "cacheRead" | "cacheWrite">): number {
68 return request.input + request.output + request.cacheRead + request.cacheWrite;
69}
70
71/** The tokens of each kind, in words, for a hint. */
72export function tokenKinds(request: Pick<GatewayRequest, "input" | "output" | "cacheRead" | "cacheWrite">): string {
73 const n = (v: number) => v.toLocaleString("en-US");
74 return `${n(request.input)} input, ${n(request.output)} output, ${n(request.cacheRead)} cache read, ${n(request.cacheWrite)} cache write`;
75}
76
77/** A request's cache tokens in words, for a hint: read, written, and how many of the writes last an hour. */
78export function cacheKinds(request: Pick<GatewayRequest, "cacheRead" | "cacheWrite" | "cacheWriteHour">): string {
79 const n = (v: number) => v.toLocaleString("en-US");
80 const hour = request.cacheWriteHour ? `, ${n(request.cacheWriteHour)} of them to the hour-long cache` : "";
81 return `${n(request.cacheRead)} read from the cache, ${n(request.cacheWrite)} written${hour}`;
82}
83
84/** How a status reads, and how much it matters. */
85export function statusTone(status: number): { label: string; tone: "success" | "warn" | "danger" | "neutral" } {
86 if (status >= 200 && status < 300) return { label: String(status), tone: "success" };
87 if (status === 402 || status === 429) return { label: String(status), tone: "warn" };
88 if (status >= 400) return { label: String(status), tone: "danger" };
89 return { label: String(status), tone: "neutral" };
90}
91
92/** A request's duration: `840 ms`, `4.2 s`, `2 min 5 s`. */
93export function duration(ms: number): string {
94 if (ms < 1_000) return `${Math.round(ms)} ms`;
95 if (ms < 60_000) return `${(ms / 1_000).toFixed(1).replace(/\.0$/, "")} s`;
96 const seconds = Math.round(ms / 1_000);
97 return `${Math.floor(seconds / 60)} min ${seconds % 60} s`;
98}