web2api-server
Version:
server part of web2api service(1/2)
213 lines (203 loc) • 5.67 kB
text/typescript
import { serverInfo } from "@/variables.js";
import { globalClientManager } from "@/ws/client-manage.js";
import { type ModelId } from "@/ws/type.js";
import { consola } from "consola/basic";
import { Hono } from "hono";
import { streamSSE } from "hono/streaming";
import { z } from "zod";
import {
ChatCompletionRequestSchema,
type ChatCompletionResponse,
type StreamingResponse,
} from "./completion-types.js";
export const completionAPIHandler = new Hono();
/**
reasoning will be deserted for now, because ai-sdk has no reasoning stream implemention now,
which lead to that the completion api of server cannot not stream reasoning before cmpl part.
*/
completionAPIHandler.post("/chat/completions", async (c) => {
try {
const req = await c.req.json();
const parsedReq = ChatCompletionRequestSchema.parse(req);
consola.verbose("/chat/completions request param:", parsedReq);
const {
model,
messages,
stream: useStream = false,
additional_parameters,
} = parsedReq;
if (globalClientManager.state !== 1) {
return c.json(errorsBuilder.notReady(), 503);
}
if (!globalClientManager.includeModel(model)) {
return c.json(errorsBuilder.modelInvalid(model), 403);
}
if (globalClientManager.clientVersion !== serverInfo.version) {
return c.json(errorsBuilder.versionIncompatible(), 503);
}
const {
stream: chatStream,
writer,
id,
} = globalClientManager.startChat(model, messages, additional_parameters);
if (useStream) {
const createdAt = Date.now();
return streamSSE(c, async (stream) => {
stream.onAbort(() => {
consola.log("Aborted!");
writer.close();
});
try {
for await (const textPart of chatStream) {
const streamResp: StreamingResponse = {
id: id,
object: "chat.completion.chunk",
created: createdAt,
model: model,
choices: [
{
delta: {
content: textPart,
role: "assistant",
},
index: 0,
finish_reason: null,
},
],
};
await stream.writeSSE({
data: JSON.stringify(streamResp),
});
}
const stopResponse: StreamingResponse = {
id: id,
object: "chat.completion.chunk",
created: createdAt,
model: model,
choices: [
{
delta: {
content: "",
role: "assistant",
},
index: 0,
finish_reason: "stop",
},
],
usage: {
completion_tokens: 0,
prompt_tokens: 0,
total_tokens: 0,
},
};
await stream.writeSSE({
data: JSON.stringify(stopResponse),
});
} catch (e: any) {
await stream.writeSSE({
data: e,
});
} finally {
stream.close();
}
});
} else {
let completeContent = "";
for await (const k of chatStream) {
completeContent += k;
}
writer.close();
const response: ChatCompletionResponse = {
id: id,
object: "chat.completion",
created: Date.now(),
model: model,
choices: [
{
message: {
role: "assistant",
content: completeContent,
},
logprobs: null,
finish_reason: "stop",
index: 0,
},
],
usage: {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
},
};
return c.json(response);
}
} catch (e) {
if (e instanceof z.ZodError) {
return c.json(
errorsBuilder.requestParam(
e.message,
e.errors
.map((_e) => _e.path)
.flat()
.join(",")
),
400
);
}
consola.error("unexpect error:", e);
return c.json(errorsBuilder.unknownError("unexpect error"), 500);
}
});
const errorsBuilder = {
modelInvalid(model: string) {
return {
error: {
// prettier-ignore
message: `The model \`${model}\` does not exist. support: ${JSON.stringify(globalClientManager.provideModels)}`,
type: "invalid_request_error",
param: null,
code: "model_not_found",
},
};
},
notReady() {
return {
error: {
message: `The chrome-extention-side of web2api-server is not ready, current state is '${globalClientManager.state}'. Please check it.`,
type: "server_error",
param: null,
code: "service_unavailable",
},
};
},
requestParam(message: string, param: string) {
return {
error: {
message: message,
type: "invalid_request_error",
param: param,
code: "bad_request",
},
};
},
versionIncompatible() {
return {
error: {
message: `serverVersion(${serverInfo.version}) is incompatible with clientVersion(${globalClientManager.clientVersion})`,
type: "invalid_request_error",
param: null,
code: "service_unavailable",
},
};
},
unknownError(message?: string) {
return {
error: {
message: message,
type: "unknown_error",
param: null,
code: "internal_service_error",
},
};
},
};