server.ts 6.8 KB

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