opencode/js/src/server/server.ts

310 lines
7.9 KiB
TypeScript
Raw Normal View History

2025-05-18 09:31:42 +08:00
import { Log } from "../util/log";
2025-05-19 02:13:04 +08:00
import { Bus } from "../bus";
2025-05-19 10:30:41 +08:00
import { describeRoute, generateSpecs, openAPISpecs } from "hono-openapi";
2025-05-19 02:13:04 +08:00
import { Hono } from "hono";
import { streamSSE } from "hono/streaming";
import { Session } from "../session/session";
2025-05-19 10:30:41 +08:00
import { resolver, validator as zValidator } from "hono-openapi/zod";
2025-05-19 02:13:04 +08:00
import { z } from "zod";
2025-05-29 00:53:22 +08:00
import { LLM } from "../llm/llm";
2025-05-29 22:21:59 +08:00
import { Message } from "../session/message";
2025-05-29 23:58:40 +08:00
import { Provider } from "../provider/provider";
2025-05-19 02:13:04 +08:00
export namespace Server {
const log = Log.create({ service: "server" });
2025-05-18 09:31:42 +08:00
const PORT = 16713;
2025-05-19 02:13:04 +08:00
2025-05-19 02:28:08 +08:00
export type App = ReturnType<typeof app>;
2025-05-19 02:13:04 +08:00
2025-05-19 02:28:08 +08:00
function app() {
2025-05-19 10:30:41 +08:00
const app = new Hono();
const result = app
.get(
"/openapi",
openAPISpecs(app, {
documentation: {
info: {
title: "opencode",
version: "1.0.0",
description: "opencode api",
},
2025-05-29 22:21:59 +08:00
openapi: "3.0.0",
2025-05-19 10:30:41 +08:00
},
}),
)
2025-05-29 23:32:55 +08:00
.get(
"/event",
describeRoute({
description: "Get events",
responses: {
200: {
description: "Event stream",
content: {
"application/json": {
schema: resolver(
Bus.payloads().openapi({
ref: "Event",
}),
),
},
},
},
},
}),
async (c) => {
log.info("event connected");
return streamSSE(c, async (stream) => {
stream.writeSSE({
data: JSON.stringify({}),
2025-05-19 02:13:04 +08:00
});
2025-05-29 23:32:55 +08:00
const unsub = Bus.subscribeAll(async (event) => {
await stream.writeSSE({
data: JSON.stringify(event),
});
});
await new Promise<void>((resolve) => {
stream.onAbort(() => {
unsub();
resolve();
log.info("event disconnected");
});
2025-05-19 02:13:04 +08:00
});
});
2025-05-29 23:32:55 +08:00
},
)
2025-05-19 10:30:41 +08:00
.post(
"/session_create",
describeRoute({
description: "Create a new session",
responses: {
200: {
description: "Successfully created session",
content: {
"application/json": {
2025-05-29 23:38:55 +08:00
schema: resolver(Session.Info),
2025-05-20 23:11:06 +08:00
},
},
},
},
}),
async (c) => {
const session = await Session.create();
return c.json(session);
},
)
2025-05-27 06:06:41 +08:00
.post(
"/session_share",
describeRoute({
description: "Share the session",
responses: {
200: {
description: "Successfully shared session",
content: {
"application/json": {
2025-05-29 23:38:55 +08:00
schema: resolver(Session.Info),
2025-05-27 06:06:41 +08:00
},
},
},
},
}),
zValidator(
"json",
z.object({
sessionID: z.string(),
}),
),
async (c) => {
const body = c.req.valid("json");
await Session.share(body.sessionID);
const session = await Session.get(body.sessionID);
return c.json(session);
},
)
2025-05-20 23:11:06 +08:00
.post(
"/session_messages",
describeRoute({
description: "Get messages for a session",
responses: {
200: {
description: "Successfully created session",
content: {
"application/json": {
2025-05-29 22:21:59 +08:00
schema: resolver(Message.Info.array()),
2025-05-19 10:30:41 +08:00
},
},
},
},
}),
2025-05-27 06:10:10 +08:00
zValidator(
"json",
z.object({
sessionID: z.string(),
}),
),
2025-05-19 10:30:41 +08:00
async (c) => {
2025-05-27 06:10:10 +08:00
const messages = await Session.messages(
c.req.valid("json").sessionID,
);
return c.json(messages);
2025-05-19 10:30:41 +08:00
},
)
2025-05-28 03:34:46 +08:00
.post(
"/session_list",
describeRoute({
description: "List all sessions",
responses: {
200: {
description: "List of sessions",
content: {
"application/json": {
2025-05-28 03:35:31 +08:00
schema: resolver(Session.Info.array()),
2025-05-28 03:34:46 +08:00
},
},
},
},
}),
async (c) => {
const sessions = await Array.fromAsync(Session.list());
return c.json(sessions);
},
)
2025-05-29 03:07:51 +08:00
.post(
"/session_abort",
describeRoute({
description: "Abort a session",
responses: {
200: {
description: "Aborted session",
content: {
"application/json": {
schema: resolver(z.boolean()),
},
},
},
},
}),
zValidator(
"json",
z.object({
sessionID: z.string(),
}),
),
async (c) => {
const body = c.req.valid("json");
return c.json(Session.abort(body.sessionID));
},
2025-05-30 01:17:56 +08:00
)
.post(
"/session_summarize",
describeRoute({
description: "Summarize the session",
responses: {
200: {
description: "Summarize the session",
content: {
"application/json": {
schema: resolver(z.boolean()),
},
},
},
},
}),
zValidator(
"json",
z.object({
sessionID: z.string(),
providerID: z.string(),
modelID: z.string(),
}),
),
async (c) => {
const body = c.req.valid("json");
await Session.summarize(body);
return c.json(true);
},
2025-05-29 03:07:51 +08:00
)
2025-05-19 02:13:04 +08:00
.post(
"/session_chat",
2025-05-29 03:07:51 +08:00
describeRoute({
description: "Chat with a model",
responses: {
200: {
description: "Chat with a model",
content: {
"application/json": {
2025-05-29 22:21:59 +08:00
schema: resolver(Message.Info),
2025-05-29 03:07:51 +08:00
},
},
},
},
}),
2025-05-19 02:13:04 +08:00
zValidator(
"json",
z.object({
sessionID: z.string(),
2025-05-29 00:53:22 +08:00
providerID: z.string(),
modelID: z.string(),
2025-05-29 22:21:59 +08:00
parts: Message.Part.array(),
2025-05-19 02:13:04 +08:00
}),
),
async (c) => {
const body = c.req.valid("json");
2025-05-29 03:39:51 +08:00
const msg = await Session.chat(body);
2025-05-19 02:13:04 +08:00
return c.json(msg);
2025-05-18 09:31:42 +08:00
},
2025-05-29 00:53:22 +08:00
)
.post(
"/provider_list",
describeRoute({
description: "List all providers",
responses: {
200: {
description: "List of providers",
content: {
"application/json": {
2025-05-29 23:58:40 +08:00
schema: resolver(z.record(z.string(), Provider.Info)),
2025-05-29 00:53:22 +08:00
},
},
},
},
}),
async (c) => {
const providers = await LLM.providers();
2025-05-29 23:58:40 +08:00
const result: Record<string, Provider.Info> = {};
2025-05-29 00:53:22 +08:00
for (const [providerID, provider] of Object.entries(providers)) {
result[providerID] = provider.info;
}
return c.json(result);
},
2025-05-19 02:13:04 +08:00
);
2025-05-19 10:30:41 +08:00
return result;
}
export async function openapi() {
const a = app();
const result = await generateSpecs(a, {
documentation: {
info: {
title: "opencode",
version: "1.0.0",
description: "opencode api",
},
2025-05-29 22:21:59 +08:00
openapi: "3.0.0",
2025-05-19 10:30:41 +08:00
},
});
return result;
2025-05-19 02:28:08 +08:00
}
2025-05-19 02:13:04 +08:00
2025-05-19 02:28:08 +08:00
export function listen() {
const server = Bun.serve({
2025-05-19 02:13:04 +08:00
port: PORT,
hostname: "0.0.0.0",
idleTimeout: 0,
2025-05-19 02:28:08 +08:00
fetch: app().fetch,
2025-05-19 02:13:04 +08:00
});
2025-05-19 02:28:08 +08:00
return server;
2025-05-18 09:31:42 +08:00
}
}