server.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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 { LLM } from "../llm/llm";
  10. import { Message } from "../session/message";
  11. import { Provider } from "../provider/provider";
  12. export namespace Server {
  13. const log = Log.create({ service: "server" });
  14. const PORT = 16713;
  15. export type App = ReturnType<typeof app>;
  16. function app() {
  17. const app = new Hono();
  18. const result = app
  19. .get(
  20. "/openapi",
  21. openAPISpecs(app, {
  22. documentation: {
  23. info: {
  24. title: "opencode",
  25. version: "1.0.0",
  26. description: "opencode api",
  27. },
  28. openapi: "3.0.0",
  29. },
  30. }),
  31. )
  32. .get(
  33. "/event",
  34. describeRoute({
  35. description: "Get events",
  36. responses: {
  37. 200: {
  38. description: "Event stream",
  39. content: {
  40. "application/json": {
  41. schema: resolver(
  42. Bus.payloads().openapi({
  43. ref: "Event",
  44. }),
  45. ),
  46. },
  47. },
  48. },
  49. },
  50. }),
  51. async (c) => {
  52. log.info("event connected");
  53. return streamSSE(c, async (stream) => {
  54. stream.writeSSE({
  55. data: JSON.stringify({}),
  56. });
  57. const unsub = Bus.subscribeAll(async (event) => {
  58. await stream.writeSSE({
  59. data: JSON.stringify(event),
  60. });
  61. });
  62. await new Promise<void>((resolve) => {
  63. stream.onAbort(() => {
  64. unsub();
  65. resolve();
  66. log.info("event disconnected");
  67. });
  68. });
  69. });
  70. },
  71. )
  72. .post(
  73. "/session_create",
  74. describeRoute({
  75. description: "Create a new session",
  76. responses: {
  77. 200: {
  78. description: "Successfully created session",
  79. content: {
  80. "application/json": {
  81. schema: resolver(Session.Info),
  82. },
  83. },
  84. },
  85. },
  86. }),
  87. async (c) => {
  88. const session = await Session.create();
  89. return c.json(session);
  90. },
  91. )
  92. .post(
  93. "/session_share",
  94. describeRoute({
  95. description: "Share the session",
  96. responses: {
  97. 200: {
  98. description: "Successfully shared session",
  99. content: {
  100. "application/json": {
  101. schema: resolver(Session.Info),
  102. },
  103. },
  104. },
  105. },
  106. }),
  107. zValidator(
  108. "json",
  109. z.object({
  110. sessionID: z.string(),
  111. }),
  112. ),
  113. async (c) => {
  114. const body = c.req.valid("json");
  115. await Session.share(body.sessionID);
  116. const session = await Session.get(body.sessionID);
  117. return c.json(session);
  118. },
  119. )
  120. .post(
  121. "/session_messages",
  122. describeRoute({
  123. description: "Get messages for a session",
  124. responses: {
  125. 200: {
  126. description: "Successfully created session",
  127. content: {
  128. "application/json": {
  129. schema: resolver(Message.Info.array()),
  130. },
  131. },
  132. },
  133. },
  134. }),
  135. zValidator(
  136. "json",
  137. z.object({
  138. sessionID: z.string(),
  139. }),
  140. ),
  141. async (c) => {
  142. const messages = await Session.messages(
  143. c.req.valid("json").sessionID,
  144. );
  145. return c.json(messages);
  146. },
  147. )
  148. .post(
  149. "/session_list",
  150. describeRoute({
  151. description: "List all sessions",
  152. responses: {
  153. 200: {
  154. description: "List of sessions",
  155. content: {
  156. "application/json": {
  157. schema: resolver(Session.Info.array()),
  158. },
  159. },
  160. },
  161. },
  162. }),
  163. async (c) => {
  164. const sessions = await Array.fromAsync(Session.list());
  165. return c.json(sessions);
  166. },
  167. )
  168. .post(
  169. "/session_abort",
  170. describeRoute({
  171. description: "Abort a session",
  172. responses: {
  173. 200: {
  174. description: "Aborted session",
  175. content: {
  176. "application/json": {
  177. schema: resolver(z.boolean()),
  178. },
  179. },
  180. },
  181. },
  182. }),
  183. zValidator(
  184. "json",
  185. z.object({
  186. sessionID: z.string(),
  187. }),
  188. ),
  189. async (c) => {
  190. const body = c.req.valid("json");
  191. return c.json(Session.abort(body.sessionID));
  192. },
  193. )
  194. .post(
  195. "/session_summarize",
  196. describeRoute({
  197. description: "Summarize the session",
  198. responses: {
  199. 200: {
  200. description: "Summarize the session",
  201. content: {
  202. "application/json": {
  203. schema: resolver(z.boolean()),
  204. },
  205. },
  206. },
  207. },
  208. }),
  209. zValidator(
  210. "json",
  211. z.object({
  212. sessionID: z.string(),
  213. providerID: z.string(),
  214. modelID: z.string(),
  215. }),
  216. ),
  217. async (c) => {
  218. const body = c.req.valid("json");
  219. await Session.summarize(body);
  220. return c.json(true);
  221. },
  222. )
  223. .post(
  224. "/session_chat",
  225. describeRoute({
  226. description: "Chat with a model",
  227. responses: {
  228. 200: {
  229. description: "Chat with a model",
  230. content: {
  231. "application/json": {
  232. schema: resolver(Message.Info),
  233. },
  234. },
  235. },
  236. },
  237. }),
  238. zValidator(
  239. "json",
  240. z.object({
  241. sessionID: z.string(),
  242. providerID: z.string(),
  243. modelID: z.string(),
  244. parts: Message.Part.array(),
  245. }),
  246. ),
  247. async (c) => {
  248. const body = c.req.valid("json");
  249. const msg = await Session.chat(body);
  250. return c.json(msg);
  251. },
  252. )
  253. .post(
  254. "/provider_list",
  255. describeRoute({
  256. description: "List all providers",
  257. responses: {
  258. 200: {
  259. description: "List of providers",
  260. content: {
  261. "application/json": {
  262. schema: resolver(Provider.Info.array()),
  263. },
  264. },
  265. },
  266. },
  267. }),
  268. async (c) => {
  269. const providers = await LLM.providers();
  270. const result = [] as (Provider.Info & { key: string })[];
  271. for (const [key, provider] of Object.entries(providers)) {
  272. result.push({ ...provider.info, key });
  273. }
  274. return c.json(result);
  275. },
  276. );
  277. return result;
  278. }
  279. export async function openapi() {
  280. const a = app();
  281. const result = await generateSpecs(a, {
  282. documentation: {
  283. info: {
  284. title: "opencode",
  285. version: "1.0.0",
  286. description: "opencode api",
  287. },
  288. openapi: "3.0.0",
  289. },
  290. });
  291. return result;
  292. }
  293. export function listen() {
  294. const server = Bun.serve({
  295. port: PORT,
  296. hostname: "0.0.0.0",
  297. idleTimeout: 0,
  298. fetch: app().fetch,
  299. });
  300. return server;
  301. }
  302. }