Skip to content

Commit e4ae8c4

Browse files
committed
Update to actual version of ChatGPT Next Web
1 parent bc6caa4 commit e4ae8c4

File tree

175 files changed

+12399
-9896
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

175 files changed

+12399
-9896
lines changed

app/api/anthropic/[...path]/route.ts

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import { getServerSideConfig } from "@/app/config/server";
2+
import {
3+
ANTHROPIC_BASE_URL,
4+
Anthropic,
5+
ApiPath,
6+
DEFAULT_MODELS,
7+
ModelProvider,
8+
} from "@/app/constant";
9+
import { prettyObject } from "@/app/utils/format";
10+
import { NextRequest, NextResponse } from "next/server";
11+
import { auth } from "../../auth";
12+
import { collectModelTable } from "@/app/utils/model";
13+
14+
const ALLOWD_PATH = new Set([Anthropic.ChatPath, Anthropic.ChatPath1]);
15+
16+
async function handle(
17+
req: NextRequest,
18+
{ params }: { params: { path: string[] } },
19+
) {
20+
console.log("[Anthropic Route] params ", params);
21+
22+
if (req.method === "OPTIONS") {
23+
return NextResponse.json({ body: "OK" }, { status: 200 });
24+
}
25+
26+
const subpath = params.path.join("/");
27+
28+
if (!ALLOWD_PATH.has(subpath)) {
29+
console.log("[Anthropic Route] forbidden path ", subpath);
30+
return NextResponse.json(
31+
{
32+
error: true,
33+
msg: "you are not allowed to request " + subpath,
34+
},
35+
{
36+
status: 403,
37+
},
38+
);
39+
}
40+
41+
const authResult = auth(req, ModelProvider.Claude);
42+
if (authResult.error) {
43+
return NextResponse.json(authResult, {
44+
status: 401,
45+
});
46+
}
47+
48+
try {
49+
const response = await request(req);
50+
return response;
51+
} catch (e) {
52+
console.error("[Anthropic] ", e);
53+
return NextResponse.json(prettyObject(e));
54+
}
55+
}
56+
57+
export const GET = handle;
58+
export const POST = handle;
59+
60+
export const runtime = "edge";
61+
export const preferredRegion = [
62+
"arn1",
63+
"bom1",
64+
"cdg1",
65+
"cle1",
66+
"cpt1",
67+
"dub1",
68+
"fra1",
69+
"gru1",
70+
"hnd1",
71+
"iad1",
72+
"icn1",
73+
"kix1",
74+
"lhr1",
75+
"pdx1",
76+
"sfo1",
77+
"sin1",
78+
"syd1",
79+
];
80+
81+
const serverConfig = getServerSideConfig();
82+
83+
async function request(req: NextRequest) {
84+
const controller = new AbortController();
85+
86+
let authHeaderName = "x-api-key";
87+
let authValue =
88+
req.headers.get(authHeaderName) ||
89+
req.headers.get("Authorization")?.replaceAll("Bearer ", "").trim() ||
90+
serverConfig.anthropicApiKey ||
91+
"";
92+
93+
let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.Anthropic, "");
94+
95+
let baseUrl =
96+
serverConfig.anthropicUrl || serverConfig.baseUrl || ANTHROPIC_BASE_URL;
97+
98+
if (!baseUrl.startsWith("http")) {
99+
baseUrl = `https://${baseUrl}`;
100+
}
101+
102+
if (baseUrl.endsWith("/")) {
103+
baseUrl = baseUrl.slice(0, -1);
104+
}
105+
106+
console.log("[Proxy] ", path);
107+
console.log("[Base Url]", baseUrl);
108+
109+
const timeoutId = setTimeout(
110+
() => {
111+
controller.abort();
112+
},
113+
10 * 60 * 1000,
114+
);
115+
116+
const fetchUrl = `${baseUrl}${path}`;
117+
118+
const fetchOptions: RequestInit = {
119+
headers: {
120+
"Content-Type": "application/json",
121+
"Cache-Control": "no-store",
122+
[authHeaderName]: authValue,
123+
"anthropic-version":
124+
req.headers.get("anthropic-version") ||
125+
serverConfig.anthropicApiVersion ||
126+
Anthropic.Vision,
127+
},
128+
method: req.method,
129+
body: req.body,
130+
redirect: "manual",
131+
// @ts-ignore
132+
duplex: "half",
133+
signal: controller.signal,
134+
};
135+
136+
// #1815 try to refuse some request to some models
137+
if (serverConfig.customModels && req.body) {
138+
try {
139+
const modelTable = collectModelTable(
140+
DEFAULT_MODELS,
141+
serverConfig.customModels,
142+
);
143+
const clonedBody = await req.text();
144+
fetchOptions.body = clonedBody;
145+
146+
const jsonBody = JSON.parse(clonedBody) as { model?: string };
147+
148+
// not undefined and is false
149+
if (modelTable[jsonBody?.model ?? ""].available === false) {
150+
return NextResponse.json(
151+
{
152+
error: true,
153+
message: `you are not allowed to use ${jsonBody?.model} model`,
154+
},
155+
{
156+
status: 403,
157+
},
158+
);
159+
}
160+
} catch (e) {
161+
console.error(`[Anthropic] filter`, e);
162+
}
163+
}
164+
console.log("[Anthropic request]", fetchOptions.headers, req.method);
165+
try {
166+
const res = await fetch(fetchUrl, fetchOptions);
167+
168+
console.log(
169+
"[Anthropic response]",
170+
res.status,
171+
" ",
172+
res.headers,
173+
res.url,
174+
);
175+
// to prevent browser prompt for credentials
176+
const newHeaders = new Headers(res.headers);
177+
newHeaders.delete("www-authenticate");
178+
// to disable nginx buffering
179+
newHeaders.set("X-Accel-Buffering", "no");
180+
181+
return new Response(res.body, {
182+
status: res.status,
183+
statusText: res.statusText,
184+
headers: newHeaders,
185+
});
186+
} finally {
187+
clearTimeout(timeoutId);
188+
}
189+
}

app/api/auth.ts

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NextRequest } from "next/server";
22
import { getServerSideConfig } from "../config/server";
3-
import binary from "spark-md5";
4-
import { ACCESS_CODE_PREFIX } from "../constant";
3+
import md5 from "spark-md5";
4+
import { ACCESS_CODE_PREFIX, ModelProvider } from "../constant";
55

66
function getIP(req: NextRequest) {
77
let ip = req.ip ?? req.headers.get("x-real-ip");
@@ -16,21 +16,21 @@ function getIP(req: NextRequest) {
1616

1717
function parseApiKey(bearToken: string) {
1818
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
19-
const isOpenAiKey = !token.startsWith(ACCESS_CODE_PREFIX);
19+
const isApiKey = !token.startsWith(ACCESS_CODE_PREFIX);
2020

2121
return {
22-
accessCode: isOpenAiKey ? "" : token.slice(ACCESS_CODE_PREFIX.length),
23-
apiKey: isOpenAiKey ? token : "",
22+
accessCode: isApiKey ? "" : token.slice(ACCESS_CODE_PREFIX.length),
23+
apiKey: isApiKey ? token : "",
2424
};
2525
}
2626

27-
export function auth(req: NextRequest) {
27+
export function auth(req: NextRequest, modelProvider: ModelProvider) {
2828
const authToken = req.headers.get("Authorization") ?? "";
2929

3030
// check if it is openai api key or user token
31-
const { accessCode, apiKey: token } = parseApiKey(authToken);
31+
const { accessCode, apiKey } = parseApiKey(authToken);
3232

33-
const hashedCode = binary.hash(accessCode ?? "").trim();
33+
const hashedCode = md5.hash(accessCode ?? "").trim();
3434

3535
const serverConfig = getServerSideConfig();
3636
console.log("[Auth] allowed hashed codes: ", [...serverConfig.codes]);
@@ -39,22 +39,57 @@ export function auth(req: NextRequest) {
3939
console.log("[User IP] ", getIP(req));
4040
console.log("[Time] ", new Date().toLocaleString());
4141

42-
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
42+
if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !apiKey) {
4343
return {
4444
error: true,
4545
msg: !accessCode ? "empty access code" : "wrong access code",
4646
};
4747
}
4848

49-
// Check if the access code has a corresponding API key
50-
const apiKey = serverConfig.apiKeys.get(hashedCode);
51-
if (apiKey) {
52-
console.log("[Auth] use access code-specific API key");
53-
req.headers.set("Authorization", `Bearer ${apiKey}`);
54-
} else if (token) {
55-
console.log("[Auth] use user API key");
49+
if (serverConfig.hideUserApiKey && !!apiKey) {
50+
return {
51+
error: true,
52+
msg: "you are not allowed to access with your own api key",
53+
};
54+
}
55+
56+
// if user does not provide an api key, inject system api key
57+
if (!apiKey) {
58+
const serverConfig = getServerSideConfig();
59+
60+
// const systemApiKey =
61+
// modelProvider === ModelProvider.GeminiPro
62+
// ? serverConfig.googleApiKey
63+
// : serverConfig.isAzure
64+
// ? serverConfig.azureApiKey
65+
// : serverConfig.apiKey;
66+
67+
let systemApiKey: string | undefined;
68+
69+
switch (modelProvider) {
70+
case ModelProvider.GeminiPro:
71+
systemApiKey = serverConfig.googleApiKey;
72+
break;
73+
case ModelProvider.Claude:
74+
systemApiKey = serverConfig.anthropicApiKey;
75+
break;
76+
case ModelProvider.GPT:
77+
default:
78+
if (serverConfig.isAzure) {
79+
systemApiKey = serverConfig.azureApiKey;
80+
} else {
81+
systemApiKey = serverConfig.apiKey;
82+
}
83+
}
84+
85+
if (systemApiKey) {
86+
console.log("[Auth] use system api key");
87+
req.headers.set("Authorization", `Bearer ${systemApiKey}`);
88+
} else {
89+
console.log("[Auth] admin did not provide an api key");
90+
}
5691
} else {
57-
console.log("[Auth] admin did not provide an API key");
92+
console.log("[Auth] use user api key");
5893
}
5994

6095
return {

0 commit comments

Comments
 (0)