Skip to content

Commit 96b57d9

Browse files
evaeva
authored andcommitted
feat: add AnyAPI provider
Adds AnyAPI (https://anyapi.ai) as a new AI provider option. AnyAPI is a unified AI platform that provides access to hundreds of AI models from OpenAI, Anthropic, Google, Meta, DeepSeek and more through a single OpenAI-compatible API endpoint. Changes: - app/constant.ts: add ANYAPI_BASE_URL, ApiPath, ServiceProvider, ModelProvider, AnyAPI config, model list and DEFAULT_MODELS entry - app/api/anyapi.ts: new API proxy handler - app/client/platforms/anyapi.ts: new client platform class - app/api/[provider]/route.ts: register anyapiHandler - app/api/auth.ts: add AnyAPI auth case - app/client/api.ts: add AnyAPI to ClientApi, getHeaders, getClientApi - app/components/settings.tsx: add AnyAPI config component in UI - app/config/server.ts: add ANYAPI_URL/ANYAPI_API_KEY env vars - app/store/access.ts: add anyapiUrl/anyapiApiKey store state - app/locales/en.ts, app/locales/cn.ts: add AnyAPI translations - README.md, README_CN.md: add env var documentation
1 parent 89b8f26 commit 96b57d9

13 files changed

Lines changed: 559 additions & 0 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,14 @@ SiliconFlow API URL.
375375

376376
302.AI API URL.
377377

378+
### `ANYAPI_API_KEY` (optional)
379+
380+
AnyAPI API Key.
381+
382+
### `ANYAPI_URL` (optional)
383+
384+
AnyAPI API URL.
385+
378386
## Requirements
379387

380388
NodeJS >= 18, Docker >= 20

README_CN.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,14 @@ SiliconFlow API URL.
292292

293293
302.AI API URL.
294294

295+
### `ANYAPI_API_KEY` (optional)
296+
297+
AnyAPI API Key.
298+
299+
### `ANYAPI_URL` (optional)
300+
301+
AnyAPI API URL.
302+
295303
## 开发
296304

297305
点击下方按钮,开始二次开发:

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { handle as xaiHandler } from "../../xai";
1616
import { handle as chatglmHandler } from "../../glm";
1717
import { handle as proxyHandler } from "../../proxy";
1818
import { handle as ai302Handler } from "../../302ai";
19+
import { handle as anyapiHandler } from "../../anyapi";
1920

2021
async function handle(
2122
req: NextRequest,
@@ -55,6 +56,8 @@ async function handle(
5556
return openaiHandler(req, { params });
5657
case ApiPath["302.AI"]:
5758
return ai302Handler(req, { params });
59+
case ApiPath.AnyAPI:
60+
return anyapiHandler(req, { params });
5861
default:
5962
return proxyHandler(req, { params });
6063
}

app/api/anyapi.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { getServerSideConfig } from "@/app/config/server";
2+
import {
3+
ANYAPI_BASE_URL,
4+
ApiPath,
5+
ModelProvider,
6+
ServiceProvider,
7+
} from "@/app/constant";
8+
import { prettyObject } from "@/app/utils/format";
9+
import { NextRequest, NextResponse } from "next/server";
10+
import { auth } from "@/app/api/auth";
11+
import { isModelNotavailableInServer } from "@/app/utils/model";
12+
13+
const serverConfig = getServerSideConfig();
14+
15+
export async function handle(
16+
req: NextRequest,
17+
{ params }: { params: { path: string[] } },
18+
) {
19+
console.log("[AnyAPI Route] params ", params);
20+
21+
if (req.method === "OPTIONS") {
22+
return NextResponse.json({ body: "OK" }, { status: 200 });
23+
}
24+
25+
const authResult = auth(req, ModelProvider.AnyAPI);
26+
if (authResult.error) {
27+
return NextResponse.json(authResult, {
28+
status: 401,
29+
});
30+
}
31+
32+
try {
33+
const response = await request(req);
34+
return response;
35+
} catch (e) {
36+
console.error("[AnyAPI] ", e);
37+
return NextResponse.json(prettyObject(e));
38+
}
39+
}
40+
41+
async function request(req: NextRequest) {
42+
const controller = new AbortController();
43+
44+
let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.AnyAPI, "");
45+
46+
let baseUrl = serverConfig.anyapiUrl || ANYAPI_BASE_URL;
47+
48+
if (!baseUrl.startsWith("http")) {
49+
baseUrl = `https://${baseUrl}`;
50+
}
51+
52+
if (baseUrl.endsWith("/")) {
53+
baseUrl = baseUrl.slice(0, -1);
54+
}
55+
56+
console.log("[Proxy] ", path);
57+
console.log("[Base Url]", baseUrl);
58+
59+
const timeoutId = setTimeout(
60+
() => {
61+
controller.abort();
62+
},
63+
10 * 60 * 1000,
64+
);
65+
66+
const fetchUrl = `${baseUrl}${path}`;
67+
const fetchOptions: RequestInit = {
68+
headers: {
69+
"Content-Type": "application/json",
70+
Authorization: req.headers.get("Authorization") ?? "",
71+
},
72+
method: req.method,
73+
body: req.body,
74+
redirect: "manual",
75+
// @ts-ignore
76+
duplex: "half",
77+
signal: controller.signal,
78+
};
79+
80+
// #1815 try to refuse some request to some models
81+
if (serverConfig.customModels && req.body) {
82+
try {
83+
const clonedBody = await req.text();
84+
fetchOptions.body = clonedBody;
85+
86+
const jsonBody = JSON.parse(clonedBody) as { model?: string };
87+
88+
// not undefined and is false
89+
if (
90+
isModelNotavailableInServer(
91+
serverConfig.customModels,
92+
jsonBody?.model as string,
93+
ServiceProvider.AnyAPI as string,
94+
)
95+
) {
96+
return NextResponse.json(
97+
{
98+
error: true,
99+
message: `you are not allowed to use ${jsonBody?.model} model`,
100+
},
101+
{
102+
status: 403,
103+
},
104+
);
105+
}
106+
} catch (e) {
107+
console.error(`[AnyAPI] filter`, e);
108+
}
109+
}
110+
try {
111+
const res = await fetch(fetchUrl, fetchOptions);
112+
113+
// to prevent browser prompt for credentials
114+
const newHeaders = new Headers(res.headers);
115+
newHeaders.delete("www-authenticate");
116+
// to disable nginx buffering
117+
newHeaders.set("X-Accel-Buffering", "no");
118+
119+
return new Response(res.body, {
120+
status: res.status,
121+
statusText: res.statusText,
122+
headers: newHeaders,
123+
});
124+
} finally {
125+
clearTimeout(timeoutId);
126+
}
127+
}

app/api/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
104104
case ModelProvider.SiliconFlow:
105105
systemApiKey = serverConfig.siliconFlowApiKey;
106106
break;
107+
case ModelProvider.AnyAPI:
108+
systemApiKey = serverConfig.anyapiApiKey;
109+
break;
107110
case ModelProvider.GPT:
108111
default:
109112
if (req.nextUrl.pathname.includes("azure/deployments")) {

app/client/api.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { XAIApi } from "./platforms/xai";
2525
import { ChatGLMApi } from "./platforms/glm";
2626
import { SiliconflowApi } from "./platforms/siliconflow";
2727
import { Ai302Api } from "./platforms/ai302";
28+
import { AnyAPIApi } from "./platforms/anyapi";
2829

2930
export const ROLES = ["system", "user", "assistant"] as const;
3031
export type MessageRole = (typeof ROLES)[number];
@@ -177,6 +178,9 @@ export class ClientApi {
177178
case ModelProvider["302.AI"]:
178179
this.llm = new Ai302Api();
179180
break;
181+
case ModelProvider.AnyAPI:
182+
this.llm = new AnyAPIApi();
183+
break;
180184
default:
181185
this.llm = new ChatGPTApi();
182186
}
@@ -270,6 +274,8 @@ export function getHeaders(ignoreHeaders: boolean = false) {
270274
const isSiliconFlow =
271275
modelConfig.providerName === ServiceProvider.SiliconFlow;
272276
const isAI302 = modelConfig.providerName === ServiceProvider["302.AI"];
277+
const isAnyAPI =
278+
modelConfig.providerName === ServiceProvider.AnyAPI;
273279
const isEnabledAccessControl = accessStore.enabledAccessControl();
274280
const apiKey = isGoogle
275281
? accessStore.googleApiKey
@@ -297,6 +303,8 @@ export function getHeaders(ignoreHeaders: boolean = false) {
297303
: ""
298304
: isAI302
299305
? accessStore.ai302ApiKey
306+
: isAnyAPI
307+
? accessStore.anyapiApiKey
300308
: accessStore.openaiApiKey;
301309
return {
302310
isGoogle,
@@ -312,6 +320,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
312320
isChatGLM,
313321
isSiliconFlow,
314322
isAI302,
323+
isAnyAPI,
315324
apiKey,
316325
isEnabledAccessControl,
317326
};
@@ -341,6 +350,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
341350
isChatGLM,
342351
isSiliconFlow,
343352
isAI302,
353+
isAnyAPI,
344354
apiKey,
345355
isEnabledAccessControl,
346356
} = getConfig();
@@ -393,6 +403,8 @@ export function getClientApi(provider: ServiceProvider): ClientApi {
393403
return new ClientApi(ModelProvider.SiliconFlow);
394404
case ServiceProvider["302.AI"]:
395405
return new ClientApi(ModelProvider["302.AI"]);
406+
case ServiceProvider.AnyAPI:
407+
return new ClientApi(ModelProvider.AnyAPI);
396408
default:
397409
return new ClientApi(ModelProvider.GPT);
398410
}

0 commit comments

Comments
 (0)