Public
HTTP
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
import { parseBearerString } from "https://esm.town/v/andreterron/parseBearerString";
import { API_URL } from "https://esm.town/v/std/API_URL?v=5";
import { OpenAIUsage } from "https://esm.town/v/std/OpenAIUsage";
import { RateLimit } from "npm:@rlimit/http";
const client = new OpenAIUsage();
const allowedPathnames = [
"/",
"/v1/chat/completions",
];
const rlimit = new RateLimit({
namespace: Deno.env.get("rlimit_namespace"),
maximum: 10,
interval: "1m",
});
type User = { id: string; username: string; tier: string };
export default async function(req: Request): Promise<Response> {
// Ensure only allowed pathnames are used
const { pathname, search } = new URL(req.url);
if (!allowedPathnames.includes(pathname)) {
return new Response("Path not supported", { status: 404 });
}
// Checks the user identity
const authHeader = req.headers.get("Proxy-Authorization") || req.headers.get("Authorization");
const token = authHeader ? parseBearerString(authHeader) : undefined;
const meRes = await fetch(`${API_URL}/v1/me`, { headers: { Authorization: `Bearer ${token}` } });
if (!meRes.ok) {
return new Response("Unauthorized", { status: 401 });
}
const user: User = await meRes.json();
// Check rate limit
const { ok } = await rlimit.check(`user:${user.id}`);
if (!ok) {
// Rate limited
return new Response("Too Many Requests", { status: 429 });
}
// Proxy the request
const url = new URL("." + pathname, "https://api.openai.com");
url.search = search;
const headers = new Headers(req.headers);
headers.set("Host", url.hostname);
headers.set("Authorization", `Bearer ${Deno.env.get("OPENAI_API_KEY")}`);
headers.set("OpenAI-Organization", Deno.env.get("OPENAI_API_ORG"));
const modifiedBody = await limitFreeModel(req, user);
// We don't await this promise so that it's not adding to request time.
client.writeUsage({
userId: user.id,
handle: user.username,
tokens: 0,
tier: user.tier,
model: modifiedBody.model,
}).catch((e) => {
console.error(e);
});
const openAIRes = await fetch(url, {
method: req.method,
headers,
body: JSON.stringify(modifiedBody),
redirect: "manual",
});
// Remove internal header
const res = new Response(openAIRes.body, openAIRes);
res.headers.delete("openai-organization");
return res;
}
const isExpensiveModel = (model?: string) => {
if (!model) return false;
return model.includes("gpt-4") && !model.includes("-mini")
}
// Free users are limited to gpt-4o-mini. Limit pro users to 10 expensive model requests.
async function limitFreeModel(req: Request, user: User) {
const input = await req.json();
let model = "gpt-4o-mini";
if (user.tier == "pro" && isExpensiveModel(input.model)) {
const count = await client.recentGpt4Usage(user.id);
if (count <= 10) {
model = input.model;
}
}
return {
...input,
model,
};
}
// Adapted from https://blog.r0b.io/post/creating-a-proxy-with-deno/
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Comments
Nobody has commented on this val yet: be the first!
August 1, 2024