Public
HTTP (deprecated)
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Readme

OpenAI Proxy

This OpenAI API proxy injects Val Town's API keys. For usage documentation, check out https://www.val.town/v/std/openai

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/
std-openaiproxy.web.val.run
August 1, 2024