server.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. import { Log } from "../util/log"
  2. import { Bus } from "../bus"
  3. import { describeRoute, generateSpecs, openAPISpecs } from "hono-openapi"
  4. import { Hono } from "hono"
  5. import { streamSSE } from "hono/streaming"
  6. import { Session } from "../session/session"
  7. import { resolver, validator as zValidator } from "hono-openapi/zod"
  8. import { z } from "zod"
  9. import { Message } from "../session/message"
  10. import { Provider } from "../provider/provider"
  11. import { App } from "../app/app"
  12. import { Global } from "../global"
  13. export namespace Server {
  14. const log = Log.create({ service: "server" })
  15. const PORT = 16713
  16. export type Routes = ReturnType<typeof app>
  17. function app() {
  18. const app = new Hono()
  19. const result = app
  20. .use((c, next) => {
  21. log.info("request", {
  22. method: c.req.method,
  23. path: c.req.path,
  24. })
  25. return next()
  26. })
  27. .get(
  28. "/openapi",
  29. openAPISpecs(app, {
  30. documentation: {
  31. info: {
  32. title: "opencode",
  33. version: "1.0.0",
  34. description: "opencode api",
  35. },
  36. openapi: "3.0.0",
  37. },
  38. }),
  39. )
  40. .get(
  41. "/event",
  42. describeRoute({
  43. description: "Get events",
  44. responses: {
  45. 200: {
  46. description: "Event stream",
  47. content: {
  48. "application/json": {
  49. schema: resolver(
  50. Bus.payloads().openapi({
  51. ref: "Event",
  52. }),
  53. ),
  54. },
  55. },
  56. },
  57. },
  58. }),
  59. async (c) => {
  60. log.info("event connected")
  61. return streamSSE(c, async (stream) => {
  62. stream.writeSSE({
  63. data: JSON.stringify({}),
  64. })
  65. const unsub = Bus.subscribeAll(async (event) => {
  66. await stream.writeSSE({
  67. data: JSON.stringify(event),
  68. })
  69. })
  70. await new Promise<void>((resolve) => {
  71. stream.onAbort(() => {
  72. unsub()
  73. resolve()
  74. log.info("event disconnected")
  75. })
  76. })
  77. })
  78. },
  79. )
  80. .post(
  81. "/app_info",
  82. describeRoute({
  83. description: "Get app info",
  84. responses: {
  85. 200: {
  86. description: "200",
  87. content: {
  88. "application/json": {
  89. schema: resolver(App.Info),
  90. },
  91. },
  92. },
  93. },
  94. }),
  95. async (c) => {
  96. return c.json(App.info())
  97. },
  98. )
  99. .post(
  100. "/session_initialize",
  101. describeRoute({
  102. description: "Analyze the app and create an AGENTS.md file",
  103. responses: {
  104. 200: {
  105. description: "200",
  106. content: {
  107. "application/json": {
  108. schema: resolver(z.boolean()),
  109. },
  110. },
  111. },
  112. },
  113. }),
  114. zValidator(
  115. "json",
  116. z.object({
  117. sessionID: z.string(),
  118. providerID: z.string(),
  119. modelID: z.string(),
  120. }),
  121. ),
  122. async (c) => {
  123. const body = c.req.valid("json")
  124. await Session.initialize(body)
  125. return c.json(true)
  126. },
  127. )
  128. .post(
  129. "/path_get",
  130. describeRoute({
  131. description: "Get paths",
  132. responses: {
  133. 200: {
  134. description: "200",
  135. content: {
  136. "application/json": {
  137. schema: resolver(
  138. z.object({
  139. root: z.string(),
  140. data: z.string(),
  141. cwd: z.string(),
  142. config: z.string(),
  143. }),
  144. ),
  145. },
  146. },
  147. },
  148. },
  149. }),
  150. async (c) => {
  151. const app = App.info()
  152. return c.json({
  153. root: app.path.root,
  154. data: app.path.data,
  155. cwd: app.path.cwd,
  156. config: Global.config(),
  157. })
  158. },
  159. )
  160. .post(
  161. "/session_create",
  162. describeRoute({
  163. description: "Create a new session",
  164. responses: {
  165. 200: {
  166. description: "Successfully created session",
  167. content: {
  168. "application/json": {
  169. schema: resolver(Session.Info),
  170. },
  171. },
  172. },
  173. },
  174. }),
  175. async (c) => {
  176. const session = await Session.create()
  177. return c.json(session)
  178. },
  179. )
  180. .post(
  181. "/session_share",
  182. describeRoute({
  183. description: "Share the session",
  184. responses: {
  185. 200: {
  186. description: "Successfully shared session",
  187. content: {
  188. "application/json": {
  189. schema: resolver(Session.Info),
  190. },
  191. },
  192. },
  193. },
  194. }),
  195. zValidator(
  196. "json",
  197. z.object({
  198. sessionID: z.string(),
  199. }),
  200. ),
  201. async (c) => {
  202. const body = c.req.valid("json")
  203. await Session.share(body.sessionID)
  204. const session = await Session.get(body.sessionID)
  205. return c.json(session)
  206. },
  207. )
  208. .post(
  209. "/session_messages",
  210. describeRoute({
  211. description: "Get messages for a session",
  212. responses: {
  213. 200: {
  214. description: "Successfully created session",
  215. content: {
  216. "application/json": {
  217. schema: resolver(Message.Info.array()),
  218. },
  219. },
  220. },
  221. },
  222. }),
  223. zValidator(
  224. "json",
  225. z.object({
  226. sessionID: z.string(),
  227. }),
  228. ),
  229. async (c) => {
  230. const messages = await Session.messages(c.req.valid("json").sessionID)
  231. return c.json(messages)
  232. },
  233. )
  234. .post(
  235. "/session_list",
  236. describeRoute({
  237. description: "List all sessions",
  238. responses: {
  239. 200: {
  240. description: "List of sessions",
  241. content: {
  242. "application/json": {
  243. schema: resolver(Session.Info.array()),
  244. },
  245. },
  246. },
  247. },
  248. }),
  249. async (c) => {
  250. const sessions = await Array.fromAsync(Session.list())
  251. return c.json(sessions)
  252. },
  253. )
  254. .post(
  255. "/session_abort",
  256. describeRoute({
  257. description: "Abort a session",
  258. responses: {
  259. 200: {
  260. description: "Aborted session",
  261. content: {
  262. "application/json": {
  263. schema: resolver(z.boolean()),
  264. },
  265. },
  266. },
  267. },
  268. }),
  269. zValidator(
  270. "json",
  271. z.object({
  272. sessionID: z.string(),
  273. }),
  274. ),
  275. async (c) => {
  276. const body = c.req.valid("json")
  277. return c.json(Session.abort(body.sessionID))
  278. },
  279. )
  280. .post(
  281. "/session_summarize",
  282. describeRoute({
  283. description: "Summarize the session",
  284. responses: {
  285. 200: {
  286. description: "Summarize the session",
  287. content: {
  288. "application/json": {
  289. schema: resolver(z.boolean()),
  290. },
  291. },
  292. },
  293. },
  294. }),
  295. zValidator(
  296. "json",
  297. z.object({
  298. sessionID: z.string(),
  299. providerID: z.string(),
  300. modelID: z.string(),
  301. }),
  302. ),
  303. async (c) => {
  304. const body = c.req.valid("json")
  305. await Session.summarize(body)
  306. return c.json(true)
  307. },
  308. )
  309. .post(
  310. "/session_chat",
  311. describeRoute({
  312. description: "Chat with a model",
  313. responses: {
  314. 200: {
  315. description: "Chat with a model",
  316. content: {
  317. "application/json": {
  318. schema: resolver(Message.Info),
  319. },
  320. },
  321. },
  322. },
  323. }),
  324. zValidator(
  325. "json",
  326. z.object({
  327. sessionID: z.string(),
  328. providerID: z.string(),
  329. modelID: z.string(),
  330. parts: Message.Part.array(),
  331. }),
  332. ),
  333. async (c) => {
  334. const body = c.req.valid("json")
  335. const msg = await Session.chat(body)
  336. return c.json(msg)
  337. },
  338. )
  339. .post(
  340. "/provider_list",
  341. describeRoute({
  342. description: "List all providers",
  343. responses: {
  344. 200: {
  345. description: "List of providers",
  346. content: {
  347. "application/json": {
  348. schema: resolver(Provider.Info.array()),
  349. },
  350. },
  351. },
  352. },
  353. }),
  354. async (c) => {
  355. const providers = await Provider.active()
  356. return c.json(providers.values().toArray())
  357. },
  358. )
  359. return result
  360. }
  361. export async function openapi() {
  362. const a = app()
  363. const result = await generateSpecs(a, {
  364. documentation: {
  365. info: {
  366. title: "opencode",
  367. version: "1.0.0",
  368. description: "opencode api",
  369. },
  370. openapi: "3.0.0",
  371. },
  372. })
  373. return result
  374. }
  375. export function listen() {
  376. const server = Bun.serve({
  377. port: PORT,
  378. hostname: "0.0.0.0",
  379. idleTimeout: 0,
  380. fetch: app().fetch,
  381. })
  382. return server
  383. }
  384. }