| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- import { Hono, type Context } from "hono"
- import { describeRoute, validator, resolver } from "hono-openapi"
- import z from "zod"
- import { Bus } from "../../bus"
- import { Session } from "../../session"
- import { TuiEvent } from "@/cli/cmd/tui/event"
- import { AppRuntime } from "@/effect/app-runtime"
- import { AsyncQueue } from "../../util/queue"
- import { errors } from "../error"
- import { lazy } from "../../util/lazy"
- const TuiRequest = z.object({
- path: z.string(),
- body: z.any(),
- })
- type TuiRequest = z.infer<typeof TuiRequest>
- const request = new AsyncQueue<TuiRequest>()
- const response = new AsyncQueue<any>()
- export async function callTui(ctx: Context) {
- const body = await ctx.req.json()
- request.push({
- path: ctx.req.path,
- body,
- })
- return response.next()
- }
- const TuiControlRoutes = new Hono()
- .get(
- "/next",
- describeRoute({
- summary: "Get next TUI request",
- description: "Retrieve the next TUI (Terminal User Interface) request from the queue for processing.",
- operationId: "tui.control.next",
- responses: {
- 200: {
- description: "Next TUI request",
- content: {
- "application/json": {
- schema: resolver(TuiRequest),
- },
- },
- },
- },
- }),
- async (c) => {
- const req = await request.next()
- return c.json(req)
- },
- )
- .post(
- "/response",
- describeRoute({
- summary: "Submit TUI response",
- description: "Submit a response to the TUI request queue to complete a pending request.",
- operationId: "tui.control.response",
- responses: {
- 200: {
- description: "Response submitted successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
- },
- },
- },
- }),
- validator("json", z.any()),
- async (c) => {
- const body = c.req.valid("json")
- response.push(body)
- return c.json(true)
- },
- )
- export const TuiRoutes = lazy(() =>
- new Hono()
- .post(
- "/append-prompt",
- describeRoute({
- summary: "Append TUI prompt",
- description: "Append prompt to the TUI",
- operationId: "tui.appendPrompt",
- responses: {
- 200: {
- description: "Prompt processed successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
- },
- },
- ...errors(400),
- },
- }),
- validator("json", TuiEvent.PromptAppend.properties),
- async (c) => {
- await Bus.publish(TuiEvent.PromptAppend, c.req.valid("json"))
- return c.json(true)
- },
- )
- .post(
- "/open-help",
- describeRoute({
- summary: "Open help dialog",
- description: "Open the help dialog in the TUI to display user assistance information.",
- operationId: "tui.openHelp",
- responses: {
- 200: {
- description: "Help dialog opened successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
- },
- },
- },
- }),
- async (c) => {
- await Bus.publish(TuiEvent.CommandExecute, {
- command: "help.show",
- })
- return c.json(true)
- },
- )
- .post(
- "/open-sessions",
- describeRoute({
- summary: "Open sessions dialog",
- description: "Open the session dialog",
- operationId: "tui.openSessions",
- responses: {
- 200: {
- description: "Session dialog opened successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
- },
- },
- },
- }),
- async (c) => {
- await Bus.publish(TuiEvent.CommandExecute, {
- command: "session.list",
- })
- return c.json(true)
- },
- )
- .post(
- "/open-themes",
- describeRoute({
- summary: "Open themes dialog",
- description: "Open the theme dialog",
- operationId: "tui.openThemes",
- responses: {
- 200: {
- description: "Theme dialog opened successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
- },
- },
- },
- }),
- async (c) => {
- await Bus.publish(TuiEvent.CommandExecute, {
- command: "session.list",
- })
- return c.json(true)
- },
- )
- .post(
- "/open-models",
- describeRoute({
- summary: "Open models dialog",
- description: "Open the model dialog",
- operationId: "tui.openModels",
- responses: {
- 200: {
- description: "Model dialog opened successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
- },
- },
- },
- }),
- async (c) => {
- await Bus.publish(TuiEvent.CommandExecute, {
- command: "model.list",
- })
- return c.json(true)
- },
- )
- .post(
- "/submit-prompt",
- describeRoute({
- summary: "Submit TUI prompt",
- description: "Submit the prompt",
- operationId: "tui.submitPrompt",
- responses: {
- 200: {
- description: "Prompt submitted successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
- },
- },
- },
- }),
- async (c) => {
- await Bus.publish(TuiEvent.CommandExecute, {
- command: "prompt.submit",
- })
- return c.json(true)
- },
- )
- .post(
- "/clear-prompt",
- describeRoute({
- summary: "Clear TUI prompt",
- description: "Clear the prompt",
- operationId: "tui.clearPrompt",
- responses: {
- 200: {
- description: "Prompt cleared successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
- },
- },
- },
- }),
- async (c) => {
- await Bus.publish(TuiEvent.CommandExecute, {
- command: "prompt.clear",
- })
- return c.json(true)
- },
- )
- .post(
- "/execute-command",
- describeRoute({
- summary: "Execute TUI command",
- description: "Execute a TUI command (e.g. agent_cycle)",
- operationId: "tui.executeCommand",
- responses: {
- 200: {
- description: "Command executed successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
- },
- },
- ...errors(400),
- },
- }),
- validator("json", z.object({ command: z.string() })),
- async (c) => {
- const command = c.req.valid("json").command
- await Bus.publish(TuiEvent.CommandExecute, {
- // @ts-expect-error
- command: {
- session_new: "session.new",
- session_share: "session.share",
- session_interrupt: "session.interrupt",
- session_compact: "session.compact",
- messages_page_up: "session.page.up",
- messages_page_down: "session.page.down",
- messages_line_up: "session.line.up",
- messages_line_down: "session.line.down",
- messages_half_page_up: "session.half.page.up",
- messages_half_page_down: "session.half.page.down",
- messages_first: "session.first",
- messages_last: "session.last",
- agent_cycle: "agent.cycle",
- }[command],
- })
- return c.json(true)
- },
- )
- .post(
- "/show-toast",
- describeRoute({
- summary: "Show TUI toast",
- description: "Show a toast notification in the TUI",
- operationId: "tui.showToast",
- responses: {
- 200: {
- description: "Toast notification shown successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
- },
- },
- },
- }),
- validator("json", TuiEvent.ToastShow.properties),
- async (c) => {
- await Bus.publish(TuiEvent.ToastShow, c.req.valid("json"))
- return c.json(true)
- },
- )
- .post(
- "/publish",
- describeRoute({
- summary: "Publish TUI event",
- description: "Publish a TUI event",
- operationId: "tui.publish",
- responses: {
- 200: {
- description: "Event published successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
- },
- },
- ...errors(400),
- },
- }),
- validator(
- "json",
- z.union(
- Object.values(TuiEvent).map((def) => {
- return z
- .object({
- type: z.literal(def.type),
- properties: def.properties,
- })
- .meta({
- ref: "Event" + "." + def.type,
- })
- }),
- ),
- ),
- async (c) => {
- const evt = c.req.valid("json")
- await Bus.publish(Object.values(TuiEvent).find((def) => def.type === evt.type)!, evt.properties)
- return c.json(true)
- },
- )
- .post(
- "/select-session",
- describeRoute({
- summary: "Select session",
- description: "Navigate the TUI to display the specified session.",
- operationId: "tui.selectSession",
- responses: {
- 200: {
- description: "Session selected successfully",
- content: {
- "application/json": {
- schema: resolver(z.boolean()),
- },
- },
- },
- ...errors(400, 404),
- },
- }),
- validator("json", TuiEvent.SessionSelect.properties),
- async (c) => {
- const { sessionID } = c.req.valid("json")
- await AppRuntime.runPromise(Session.Service.use((svc) => svc.get(sessionID)))
- await Bus.publish(TuiEvent.SessionSelect, { sessionID })
- return c.json(true)
- },
- )
- .route("/control", TuiControlRoutes),
- )
|