| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- 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 { Message } from "../session/message"
- import { Provider } from "../provider/provider"
- import { App } from "../app/app"
- import { Global } from "../global"
- export namespace Server {
- const log = Log.create({ service: "server" })
- const PORT = 16713
- export type Routes = ReturnType<typeof app>
- function app() {
- const app = new Hono()
- const result = app
- .use((c, next) => {
- log.info("request", {
- method: c.req.method,
- path: c.req.path,
- })
- return next()
- })
- .get(
- "/openapi",
- openAPISpecs(app, {
- documentation: {
- info: {
- title: "opencode",
- version: "1.0.0",
- description: "opencode api",
- },
- openapi: "3.0.0",
- },
- }),
- )
- .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({}),
- })
- 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(
- "/app_info",
- describeRoute({
- description: "Get app info",
- responses: {
- 200: {
- description: "200",
- content: {
- "application/json": {
- schema: resolver(App.Info),
- },
- },
- },
- },
- }),
- async (c) => {
- return c.json(App.info())
- },
- )
- .post(
- "/session_initialize",
- describeRoute({
- description: "Analyze the app and create an AGENTS.md file",
- responses: {
- 200: {
- description: "200",
- 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.initialize(body)
- return c.json(true)
- },
- )
- .post(
- "/path_get",
- describeRoute({
- description: "Get paths",
- responses: {
- 200: {
- description: "200",
- content: {
- "application/json": {
- schema: resolver(
- z.object({
- root: z.string(),
- data: z.string(),
- cwd: z.string(),
- config: z.string(),
- }),
- ),
- },
- },
- },
- },
- }),
- async (c) => {
- const app = App.info()
- return c.json({
- root: app.path.root,
- data: app.path.data,
- cwd: app.path.cwd,
- config: Global.config(),
- })
- },
- )
- .post(
- "/session_create",
- describeRoute({
- description: "Create a new session",
- responses: {
- 200: {
- description: "Successfully created session",
- content: {
- "application/json": {
- schema: resolver(Session.Info),
- },
- },
- },
- },
- }),
- 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(Session.Info),
- },
- },
- },
- },
- }),
- 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(Message.Info.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_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)
- },
- )
- .post(
- "/session_chat",
- describeRoute({
- description: "Chat with a model",
- responses: {
- 200: {
- description: "Chat with a model",
- content: {
- "application/json": {
- schema: resolver(Message.Info),
- },
- },
- },
- },
- }),
- zValidator(
- "json",
- z.object({
- sessionID: z.string(),
- providerID: z.string(),
- modelID: z.string(),
- parts: Message.Part.array(),
- }),
- ),
- 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(Provider.Info.array()),
- },
- },
- },
- },
- }),
- async (c) => {
- const providers = await Provider.active()
- return c.json(providers.values().toArray())
- },
- )
- 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",
- },
- openapi: "3.0.0",
- },
- })
- return result
- }
- export function listen() {
- const server = Bun.serve({
- port: PORT,
- hostname: "0.0.0.0",
- idleTimeout: 0,
- fetch: app().fetch,
- })
- return server
- }
- }
|