server.ts 10 KB

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