tui-client.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import { OpenCode, type OpenCodeEvent } from "@opencode-ai/client"
  2. export const worktree = "/tmp/opencode"
  3. export const directory = `${worktree}/packages/tui`
  4. export function json(data: unknown, init?: ResponseInit) {
  5. const headers = new Headers(init?.headers)
  6. if (!headers.has("content-type")) headers.set("content-type", "application/json")
  7. return new Response(JSON.stringify(data), {
  8. ...init,
  9. headers,
  10. })
  11. }
  12. export function createEventStream() {
  13. const encoder = new TextEncoder()
  14. const v2 = new Set<ReadableStreamDefaultController<Uint8Array>>()
  15. const pending: Uint8Array[] = []
  16. const response = (
  17. controllers: Set<ReadableStreamDefaultController<Uint8Array>>,
  18. queued: Uint8Array[],
  19. initial?: unknown,
  20. ) => {
  21. let current: ReadableStreamDefaultController<Uint8Array> | undefined
  22. return new Response(
  23. new ReadableStream<Uint8Array>({
  24. start(controller) {
  25. current = controller
  26. controllers.add(controller)
  27. if (initial) controller.enqueue(encoder.encode(`data: ${JSON.stringify(initial)}\n\n`))
  28. for (const chunk of queued.splice(0)) controller.enqueue(chunk)
  29. },
  30. cancel() {
  31. if (current) controllers.delete(current)
  32. },
  33. }),
  34. { headers: { "content-type": "text/event-stream" } },
  35. )
  36. }
  37. const send = (
  38. controllers: Set<ReadableStreamDefaultController<Uint8Array>>,
  39. queued: Uint8Array[],
  40. event: unknown,
  41. ) => {
  42. const chunk = encoder.encode(`data: ${JSON.stringify(event)}\n\n`)
  43. if (controllers.size === 0) {
  44. queued.push(chunk)
  45. return
  46. }
  47. for (const controller of controllers) controller.enqueue(chunk)
  48. }
  49. return {
  50. emit(event: OpenCodeEvent) {
  51. send(v2, pending, event)
  52. },
  53. v2() {
  54. return response(v2, pending, { id: "evt_connected", type: "server.connected", data: {} })
  55. },
  56. disconnect() {
  57. for (const controller of v2) controller.close()
  58. v2.clear()
  59. },
  60. }
  61. }
  62. export type FetchHandler = (url: URL, request: Request) => Response | undefined | Promise<Response | undefined>
  63. export function createFetch(override?: FetchHandler, events?: ReturnType<typeof createEventStream>) {
  64. const session = [] as URL[]
  65. async function fetch(input: RequestInfo | URL, init?: RequestInit) {
  66. const request = input instanceof Request ? input : new Request(input, init)
  67. const url = new URL(request.url)
  68. if (url.pathname === "/session") session.push(url)
  69. const overridden = await override?.(url, request)
  70. if (overridden) return overridden
  71. if (url.pathname === "/api/event" && events) return events.v2()
  72. if (
  73. [
  74. "/agent",
  75. "/command",
  76. "/experimental/workspace",
  77. "/experimental/workspace/status",
  78. "/formatter",
  79. "/lsp",
  80. ].includes(url.pathname)
  81. )
  82. return json([])
  83. if (["/config", "/experimental/resource", "/mcp", "/provider/auth", "/session/status"].includes(url.pathname))
  84. return json({})
  85. if (url.pathname === "/config/providers") return json({ providers: {}, default: {} })
  86. if (url.pathname === "/experimental/console") return json({ consoleManagedProviders: [], switchableOrgCount: 0 })
  87. if (url.pathname === "/experimental/capabilities") return json({ backgroundSubagents: true })
  88. if (url.pathname === "/path") return json({ home: "", state: "", config: "", worktree, directory })
  89. if (url.pathname === "/api/location")
  90. return json({ directory, project: { id: "proj_test", directory: worktree, canonical: worktree } })
  91. if (url.pathname === "/api/vcs")
  92. return json({
  93. location: { directory, project: { id: "proj_test", directory: worktree, canonical: worktree } },
  94. data: { branch: { current: "main", default: "main" } },
  95. })
  96. if (url.pathname === "/api/fs/list")
  97. return json({
  98. location: { directory, project: { id: "proj_test", directory: worktree, canonical: worktree } },
  99. data: [],
  100. })
  101. if (url.pathname === "/api/project/current") return json({ id: "proj_test", directory: worktree })
  102. if (url.pathname === "/api/project") return json([])
  103. if (url.pathname === "/api/project/proj_test/directories") return json([{ directory: worktree }])
  104. if (url.pathname === "/api/shell")
  105. return json({
  106. location: { directory, project: { id: "proj_test", directory: worktree, canonical: worktree } },
  107. data: [],
  108. })
  109. if (url.pathname === "/api/mcp")
  110. return json({
  111. location: { directory, project: { id: "proj_test", directory: worktree, canonical: worktree } },
  112. data: [],
  113. })
  114. if (url.pathname === "/api/mcp/resource")
  115. return json({
  116. location: { directory, project: { id: "proj_test", directory: worktree, canonical: worktree } },
  117. data: { resources: [], templates: [] },
  118. })
  119. if (url.pathname === "/api/session") return json({ data: [], cursor: {} })
  120. if (url.pathname === "/api/session/active") return json({ data: {} })
  121. if (url.pathname === "/api/permission/request")
  122. return json({
  123. location: { directory, project: { id: "proj_test", directory: worktree, canonical: worktree } },
  124. data: [],
  125. })
  126. if (url.pathname === "/api/form/request")
  127. return json({ location: { directory, project: { id: "proj_test", directory: worktree } }, data: [] })
  128. if (/^\/api\/session\/[^/]+\/form$/.test(url.pathname)) return json({ data: [] })
  129. if (
  130. ["/api/agent", "/api/model", "/api/provider", "/api/integration", "/api/command", "/api/skill"].includes(
  131. url.pathname,
  132. )
  133. )
  134. return json({
  135. location: { directory, project: { id: "proj_test", directory: worktree, canonical: worktree } },
  136. data: [],
  137. })
  138. if (url.pathname === "/api/reference")
  139. return json({ location: { directory, project: { id: "proj_test", directory, canonical: directory } }, data: [] })
  140. if (url.pathname === "/api/websearch/provider") {
  141. return json({ location: { directory, project: { id: "proj_test", directory, canonical: directory } }, data: [] })
  142. }
  143. if (url.pathname === "/provider") return json({ all: [], default: {}, connected: [] })
  144. if (url.pathname === "/session") return json([])
  145. if (url.pathname === "/vcs") return json({ branch: "main" })
  146. if (url.pathname === "/api/experimental/migration/v1") return json({ status: "completed" })
  147. throw new Error(`unexpected request: ${url.pathname}`)
  148. }
  149. fetch.preconnect = () => {}
  150. return { fetch, session }
  151. }
  152. export function createApi(fetch: typeof globalThis.fetch) {
  153. return OpenCode.make({ baseUrl: "http://test", fetch })
  154. }