| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- import { Log } from "../util/log";
- import { Bus } from "../bus";
- import { describeRoute, generateSpecs, openAPISpecs } from "hono-openapi";
- import { Hono } from "hono";
- import { streamSSE } from "hono/streaming";
- import { Session } from "../session/session";
- import { resolver, validator as zValidator } from "hono-openapi/zod";
- import { z } from "zod";
- import "zod-openapi/extend";
- import { Config } from "../app/config";
- import { LLM } from "../llm/llm";
- import { SessionMessage } from "./message";
- const SessionInfo = Session.Info.openapi({
- ref: "Session.Info",
- });
- const ProviderInfo = Config.Provider.openapi({
- ref: "Provider.Info",
- });
- type ProviderInfo = z.output<typeof ProviderInfo>;
- export namespace Server {
- const log = Log.create({ service: "server" });
- const PORT = 16713;
- export type App = ReturnType<typeof app>;
- function app() {
- const app = new Hono();
- const result = app
- .get(
- "/openapi",
- openAPISpecs(app, {
- documentation: {
- info: {
- title: "opencode",
- version: "1.0.0",
- description: "opencode api",
- },
- },
- }),
- )
- .get("/event", async (c) => {
- log.info("event connected");
- return streamSSE(c, async (stream) => {
- stream.writeSSE({
- data: JSON.stringify({}),
- });
- 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");
- });
- });
- });
- })
- .post(
- "/session_create",
- describeRoute({
- description: "Create a new session",
- responses: {
- 200: {
- description: "Successfully created session",
- content: {
- "application/json": {
- schema: resolver(SessionInfo),
- },
- },
- },
- },
- }),
- async (c) => {
- const session = await Session.create();
- return c.json(session);
- },
- )
- .post(
- "/session_share",
- describeRoute({
- description: "Share the session",
- responses: {
- 200: {
- description: "Successfully shared session",
- content: {
- "application/json": {
- schema: resolver(SessionInfo),
- },
- },
- },
- },
- }),
- 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);
- },
- )
- .post(
- "/session_messages",
- describeRoute({
- description: "Get messages for a session",
- responses: {
- 200: {
- description: "Successfully created session",
- content: {
- "application/json": {
- schema: resolver(SessionMessage.array()),
- },
- },
- },
- },
- }),
- zValidator(
- "json",
- z.object({
- sessionID: z.string(),
- }),
- ),
- async (c) => {
- const messages = await Session.messages(
- c.req.valid("json").sessionID,
- );
- return c.json(messages);
- },
- )
- .post(
- "/session_list",
- describeRoute({
- description: "List all sessions",
- responses: {
- 200: {
- description: "List of sessions",
- content: {
- "application/json": {
- schema: resolver(Session.Info.array()),
- },
- },
- },
- },
- }),
- async (c) => {
- const sessions = await Array.fromAsync(Session.list());
- return c.json(sessions);
- },
- )
- .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));
- },
- )
- .post(
- "/session_chat",
- describeRoute({
- description: "Chat with a model",
- responses: {
- 200: {
- description: "Chat with a model",
- content: {
- "application/json": {
- schema: resolver(SessionMessage),
- },
- },
- },
- },
- }),
- zValidator(
- "json",
- z.object({
- sessionID: z.string(),
- providerID: z.string(),
- modelID: z.string(),
- parts: SessionMessage.shape.parts,
- }),
- ),
- async (c) => {
- const body = c.req.valid("json");
- const msg = await Session.chat(body);
- return c.json(msg);
- },
- )
- .post(
- "/provider_list",
- describeRoute({
- description: "List all providers",
- responses: {
- 200: {
- description: "List of providers",
- content: {
- "application/json": {
- schema: resolver(z.record(z.string(), ProviderInfo)),
- },
- },
- },
- },
- }),
- async (c) => {
- const providers = await LLM.providers();
- const result: Record<string, ProviderInfo> = {};
- for (const [providerID, provider] of Object.entries(providers)) {
- result[providerID] = provider.info;
- }
- return c.json(result);
- },
- );
- 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",
- },
- },
- });
- return result;
- }
- export function listen() {
- const server = Bun.serve({
- port: PORT,
- hostname: "0.0.0.0",
- idleTimeout: 0,
- fetch: app().fetch,
- });
- return server;
- }
- }
|